diff options
author | joth@chromium.org <joth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-05 12:37:57 +0000 |
---|---|---|
committer | joth@chromium.org <joth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-05 12:37:57 +0000 |
commit | a598247f2a78fb8a394c5db79c39ae5cd2101697 (patch) | |
tree | 3668ae29edc3050292219ade6b5c337272ccd06d /chrome/browser/geolocation | |
parent | 6c8493c5bf17929c6cd6815e8dc80c5038cbbd29 (diff) | |
download | chromium_src-a598247f2a78fb8a394c5db79c39ae5cd2101697.zip chromium_src-a598247f2a78fb8a394c5db79c39ae5cd2101697.tar.gz chromium_src-a598247f2a78fb8a394c5db79c39ae5cd2101697.tar.bz2 |
Re-attempt at http://src.chromium.org/viewvc/chrome?view=rev&revision=37989
(asserts tidy up slit out into its own change http://codereview.chromium.org/578013/show)
Add tests for the geolocation network provider.
Also some small tidy up a few other files.
BUG=http://crbug.com/11246
TEST=unit_tests.exe --gtest_filter=NetworkLocationProvider* --gtest_break_on_failure
Review URL: http://codereview.chromium.org/578006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38207 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/geolocation')
4 files changed, 415 insertions, 13 deletions
diff --git a/chrome/browser/geolocation/location_provider.h b/chrome/browser/geolocation/location_provider.h index 762ccb1..a9960805 100644 --- a/chrome/browser/geolocation/location_provider.h +++ b/chrome/browser/geolocation/location_provider.h @@ -49,14 +49,14 @@ class LocationProviderBase // Used to inform listener that a new position fix is available or that a // fatal error has occurred. Providers should call this for new listeners // as soon as a position is available. - virtual bool LocationUpdateAvailable(LocationProviderBase* provider) = 0; + virtual void LocationUpdateAvailable(LocationProviderBase* provider) = 0; // Used to inform listener that movement has been detected. If obtaining the // position succeeds, this will be followed by a call to // LocationUpdateAvailable. Some providers may not be able to detect // movement before a new fix is obtained, so will never call this method. // Note that this is not called in response to registration of a new // listener. - virtual bool MovementDetected(LocationProviderBase* provider) = 0; + virtual void MovementDetected(LocationProviderBase* provider) = 0; protected: virtual ~ListenerInterface() {} diff --git a/chrome/browser/geolocation/network_location_provider.cc b/chrome/browser/geolocation/network_location_provider.cc index 948207d..0fb5fe1 100644 --- a/chrome/browser/geolocation/network_location_provider.cc +++ b/chrome/browser/geolocation/network_location_provider.cc @@ -80,7 +80,7 @@ class NetworkLocationProvider::PositionCache { const string16 separator(ASCIIToUTF16("|")); for (WifiData::AccessPointDataSet::const_iterator iter = wifi_data.access_point_data.begin(); - iter != wifi_data.access_point_data.begin(); + iter != wifi_data.access_point_data.end(); iter++) { *key += separator; *key += iter->mac_address; diff --git a/chrome/browser/geolocation/network_location_provider_unittest.cc b/chrome/browser/geolocation/network_location_provider_unittest.cc new file mode 100644 index 0000000..2885505 --- /dev/null +++ b/chrome/browser/geolocation/network_location_provider_unittest.cc @@ -0,0 +1,392 @@ +// Copyright (c) 2010 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/browser/geolocation/network_location_provider.h" + +#include <map> + +#include "base/json/json_reader.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/net/test_url_fetcher_factory.h" +#include "net/url_request/url_request_status.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +const char kTestServerUrl[] = "https://www.geolocation.test/service"; +const char kTestHost[] = "myclienthost.test"; +} // namespace + +// Stops the specified (nested) message loop when the listener is called back. +class MessageLoopQuitListener + : public LocationProviderBase::ListenerInterface { + public: + explicit MessageLoopQuitListener() + : client_message_loop_(MessageLoop::current()), + updated_provider_(NULL), + movement_provider_(NULL) { + DCHECK(client_message_loop_ != NULL); + } + // ListenerInterface + virtual void LocationUpdateAvailable(LocationProviderBase* provider) { + EXPECT_EQ(client_message_loop_, MessageLoop::current()); + updated_provider_ = provider; + client_message_loop_->Quit(); + } + virtual void MovementDetected(LocationProviderBase* provider) { + EXPECT_EQ(client_message_loop_, MessageLoop::current()); + movement_provider_ = provider; + client_message_loop_->Quit(); + } + MessageLoop* client_message_loop_; + LocationProviderBase* updated_provider_; + LocationProviderBase* movement_provider_; +}; + +class FakeAccessTokenStore : public LocationProviderBase::AccessTokenStore { + public: + FakeAccessTokenStore() : allow_set_(true) {} + + virtual bool SetAccessToken(const GURL& url, + const string16& access_token) { + if (!allow_set_) + return false; + token_map_[url] = access_token; + return true; + } + virtual bool GetAccessToken(const GURL& url, string16* access_token) { + std::map<GURL, string16>::iterator item = token_map_.find(url); + if (item == token_map_.end()) + return false; + *access_token = item->second; + return true; + } + bool allow_set_; + std::map<GURL, string16> token_map_; +}; + + +// A mock implementation of DeviceDataProviderImplBase for testing. Adapted from +// http://gears.googlecode.com/svn/trunk/gears/geolocation/geolocation_test.cc +template<typename DataType> +class MockDeviceDataProviderImpl + : public DeviceDataProviderImplBase<DataType> { + public: + // Factory method for use with DeviceDataProvider::SetFactory. + static DeviceDataProviderImplBase<DataType>* Create() { + return new MockDeviceDataProviderImpl<DataType>(); + } + static MockDeviceDataProviderImpl<DataType>* instance() { + CHECK(instance_ != NULL); + return instance_; + } + + MockDeviceDataProviderImpl() { + CHECK(instance_ == NULL); + instance_ = this; + } + virtual ~MockDeviceDataProviderImpl() { + CHECK(this == instance_); + instance_ = NULL; + } + + // DeviceDataProviderImplBase implementation. + virtual bool StartDataProvider() { + return true; + } + virtual bool GetData(DataType* data_out) { + CHECK(data_out); + AutoLock lock(data_mutex_); + *data_out = data_; + // We always have all the data we can get, so return true. + return true; + } + + void SetData(const DataType& new_data) { + data_mutex_.Acquire(); + const bool differs = data_.DiffersSignificantly(new_data); + data_ = new_data; + data_mutex_.Release(); + if (differs) + this->NotifyListeners(); + } + + private: + static MockDeviceDataProviderImpl<DataType>* instance_; + + DataType data_; + Lock data_mutex_; + + DISALLOW_COPY_AND_ASSIGN(MockDeviceDataProviderImpl); +}; + +template<typename DataType> +MockDeviceDataProviderImpl<DataType>* +MockDeviceDataProviderImpl<DataType>::instance_ = NULL; + +// Main test fixture +class NetworkLocationProviderTest : public testing::Test { + public: + virtual void SetUp() { + URLFetcher::set_factory(&url_fetcher_factory_); + RadioDataProvider::SetFactory( + MockDeviceDataProviderImpl<RadioData>::Create); + WifiDataProvider::SetFactory( + MockDeviceDataProviderImpl<WifiData>::Create); + } + + virtual void TearDown() { + WifiDataProvider::ResetFactory(); + RadioDataProvider::ResetFactory(); + URLFetcher::set_factory(NULL); + base::LeakTracker<URLFetcher>::CheckForLeaks(); + } + + LocationProviderBase* CreateProvider() { + return NewNetworkLocationProvider( + &access_token_store_, + NULL, // No URLContextGetter needed, as using test urlfecther factory. + test_server_url_, + ASCIIToUTF16(kTestHost)); + } + + protected: + NetworkLocationProviderTest() : test_server_url_(kTestServerUrl) {} + + static int IndexToChannal(int index) { return index + 4; } + static int IndexToAge(int index) { return (index * 3) + 100; } + + // Creates wifi data containing the specified number of access points, with + // some differentiating charactistics in each. + static WifiData CreateReferenceWifiScanData(int ap_count) { + WifiData data; + for (int i = 0; i < ap_count; ++i) { + AccessPointData ap; + ap.mac_address = ASCIIToUTF16(StringPrintf("%02d-34-56-78-54-32", i)); + ap.radio_signal_strength = i; + ap.age = IndexToAge(i); + ap.channel = IndexToChannal(i); + ap.signal_to_noise = i + 42; + ap.ssid = ASCIIToUTF16("Some nice network"); + data.access_point_data.insert(ap); + } + return data; + } + + static void ParseRequest(const std::string& request_data, + WifiData* wifi_data_out, + std::string* access_token_out) { + CHECK(wifi_data_out && access_token_out); + scoped_ptr<Value> value(base::JSONReader::Read(request_data, false)); + EXPECT_TRUE(value != NULL); + EXPECT_EQ(Value::TYPE_DICTIONARY, value->GetType()); + DictionaryValue* dictionary = static_cast<DictionaryValue*>(value.get()); + std::string attr_value; + EXPECT_TRUE(dictionary->GetString(L"version", &attr_value)); + EXPECT_EQ(attr_value, "1.1.0"); + EXPECT_TRUE(dictionary->GetString(L"host", &attr_value)); + EXPECT_EQ(attr_value, kTestHost); + // Everything else is optional. + ListValue* wifi_aps; + if (dictionary->GetList(L"wifi_towers", &wifi_aps)) { + int i = 0; + for (ListValue::const_iterator it = wifi_aps->begin(); + it < wifi_aps->end(); ++it, ++i) { + EXPECT_EQ(Value::TYPE_DICTIONARY, (*it)->GetType()); + DictionaryValue* ap = static_cast<DictionaryValue*>(*it); + AccessPointData data; + ap->GetStringAsUTF16(L"mac_address", &data.mac_address); + ap->GetInteger(L"signal_strength", &data.radio_signal_strength); + ap->GetInteger(L"age", &data.age); + ap->GetInteger(L"channel", &data.channel); + ap->GetInteger(L"signal_to_noise", &data.signal_to_noise); + ap->GetStringAsUTF16(L"ssid", &data.ssid); + wifi_data_out->access_point_data.insert(data); + } + } else { + wifi_data_out->access_point_data.clear(); + } + if (!dictionary->GetString(L"access_token", access_token_out)) + access_token_out->clear(); + } + + static void CheckEmptyRequestIsValid(const std::string& request_data) { + WifiData wifi_aps; + std::string access_token; + ParseRequest(request_data, &wifi_aps, &access_token); + EXPECT_EQ(0, static_cast<int>(wifi_aps.access_point_data.size())); + EXPECT_TRUE(access_token.empty()); + } + + static void CheckRequestIsValid(const std::string& request_data, + int expected_wifi_aps, + const std::string& expected_access_token) { + WifiData wifi_aps; + std::string access_token; + ParseRequest(request_data, &wifi_aps, &access_token); + EXPECT_EQ(expected_wifi_aps, + static_cast<int>(wifi_aps.access_point_data.size())); + WifiData expected_data = CreateReferenceWifiScanData(expected_wifi_aps); + WifiData::AccessPointDataSet::const_iterator expected = + expected_data.access_point_data.begin(); + WifiData::AccessPointDataSet::const_iterator actual = + wifi_aps.access_point_data.begin(); + for (int i = 0; i < expected_wifi_aps; ++i) { + EXPECT_EQ(expected->mac_address, actual->mac_address) << i; + EXPECT_EQ(expected->radio_signal_strength, actual->radio_signal_strength) + << i; + EXPECT_EQ(expected->age, actual->age) << i; + EXPECT_EQ(expected->channel, actual->channel) << i; + EXPECT_EQ(expected->signal_to_noise, actual->signal_to_noise) << i; + EXPECT_EQ(expected->ssid, actual->ssid) << i; + ++expected; + ++actual; + } + EXPECT_EQ(expected_access_token, access_token); + } + + const GURL test_server_url_; + MessageLoop main_message_loop_; + FakeAccessTokenStore access_token_store_; + TestURLFetcherFactory url_fetcher_factory_; +}; + + +TEST_F(NetworkLocationProviderTest, CreateDestroy) { + // Test fixture members were SetUp correctly. + EXPECT_EQ(&main_message_loop_, MessageLoop::current()); + scoped_refptr<LocationProviderBase> provider(CreateProvider()); + EXPECT_TRUE(NULL != provider.get()); + provider = NULL; + SUCCEED(); +} + +TEST_F(NetworkLocationProviderTest, StartProvider) { + scoped_refptr<LocationProviderBase> provider(CreateProvider()); + EXPECT_TRUE(provider->StartProvider()); + TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher != NULL); + + EXPECT_EQ(test_server_url_, fetcher->original_url()); + + // No wifi data so expect an empty request. + CheckEmptyRequestIsValid(fetcher->upload_data()); +} + +TEST_F(NetworkLocationProviderTest, MultipleWifiScansComplete) { + scoped_refptr<LocationProviderBase> provider(CreateProvider()); + EXPECT_TRUE(provider->StartProvider()); + TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher != NULL); + CheckEmptyRequestIsValid(fetcher->upload_data()); + // Complete the network request with bad position fix (using #define so we + // can paste this into various other strings below) + #define REFERENCE_ACCESS_TOKEN "2:k7j3G6LaL6u_lafw:4iXOeOpTh1glSXe" + const char* kNoFixNetworkResponse = + "{" + " \"location\": null," + " \"access_token\": \"" REFERENCE_ACCESS_TOKEN "\"" + "}"; + fetcher->delegate()->OnURLFetchComplete( + fetcher, test_server_url_, URLRequestStatus(), 200, // OK + ResponseCookies(), kNoFixNetworkResponse); + + // This should have set the access token anyhow + EXPECT_EQ(1, static_cast<int>(access_token_store_.token_map_.size())); + string16 token; + EXPECT_TRUE(access_token_store_.GetAccessToken(test_server_url_, &token)); + EXPECT_EQ(REFERENCE_ACCESS_TOKEN, UTF16ToUTF8(token)); + + Position position; + provider->GetPosition(&position); + EXPECT_FALSE(position.IsValidFix()); + + // Now wifi data arrives + const int kFirstScanAps = 6; + MockDeviceDataProviderImpl<WifiData>::instance()->SetData( + CreateReferenceWifiScanData(kFirstScanAps)); // Will notify listeners + fetcher = url_fetcher_factory_.GetFetcherByID(kFirstScanAps); + ASSERT_TRUE(fetcher != NULL); + // The request should have access token (set previously) and the wifi data. + CheckRequestIsValid(fetcher->upload_data(), + kFirstScanAps, + REFERENCE_ACCESS_TOKEN); + + // Send a reply with good position fix. + const char* kReferenceNetworkResponse = + "{" + " \"location\": {" + " \"latitude\": 51.0," + " \"longitude\": -0.1," + " \"altitude\": 30.1," + " \"accuracy\": 1200.4," + " \"altitude_accuracy\": 10.6" + " }" + "}"; + fetcher->delegate()->OnURLFetchComplete( + fetcher, test_server_url_, URLRequestStatus(), 200, // OK + ResponseCookies(), kReferenceNetworkResponse); + + provider->GetPosition(&position); + EXPECT_EQ(51.0, position.latitude); + EXPECT_EQ(-0.1, position.longitude); + EXPECT_EQ(30.1, position.altitude); + EXPECT_EQ(1200.4, position.accuracy); + EXPECT_EQ(10.6, position.altitude_accuracy); + EXPECT_TRUE(position.is_valid_timestamp()); + EXPECT_TRUE(position.IsValidFix()); + + // Token should still be in the store. + EXPECT_EQ(1, static_cast<int>(access_token_store_.token_map_.size())); + EXPECT_TRUE(access_token_store_.GetAccessToken(test_server_url_, &token)); + EXPECT_EQ(REFERENCE_ACCESS_TOKEN, UTF16ToUTF8(token)); + + // Wifi updated again, with one less AP. This is 'close enough' to the + // previous scan, so no new request made. + const int kSecondScanAps = kFirstScanAps - 1; + MockDeviceDataProviderImpl<WifiData>::instance()->SetData( + CreateReferenceWifiScanData(kSecondScanAps)); + fetcher = url_fetcher_factory_.GetFetcherByID(kSecondScanAps); + EXPECT_FALSE(fetcher); + + provider->GetPosition(&position); + EXPECT_EQ(51.0, position.latitude); + EXPECT_EQ(-0.1, position.longitude); + EXPECT_TRUE(position.IsValidFix()); + + // Now a third scan with more than twice the original amount -> new request. + const int kThirdScanAps = kFirstScanAps * 2 + 1; + MockDeviceDataProviderImpl<WifiData>::instance()->SetData( + CreateReferenceWifiScanData(kThirdScanAps)); + fetcher = url_fetcher_factory_.GetFetcherByID(kThirdScanAps); + EXPECT_TRUE(fetcher); + // ...reply with a network error. + fetcher->delegate()->OnURLFetchComplete( + fetcher, test_server_url_, + URLRequestStatus(URLRequestStatus::FAILED, -1), + 200, // should be ignored + ResponseCookies(), ""); + + // Error means we now no longer have a fix. + provider->GetPosition(&position); + EXPECT_FALSE(position.is_valid_latlong()); + EXPECT_FALSE(position.IsValidFix()); + + // wifi scan returns to original set: should be serviced from cache + const TestURLFetcher* orig_fetcher = + url_fetcher_factory_.GetFetcherByID(kFirstScanAps); + MockDeviceDataProviderImpl<WifiData>::instance()->SetData( + CreateReferenceWifiScanData(kFirstScanAps)); + fetcher = url_fetcher_factory_.GetFetcherByID(kFirstScanAps); + EXPECT_EQ(orig_fetcher, fetcher); // No new request created. + + provider->GetPosition(&position); + EXPECT_EQ(51.0, position.latitude); + EXPECT_EQ(-0.1, position.longitude); + EXPECT_TRUE(position.IsValidFix()); +} + +// TODO(joth): Add tests for corner cases around the 2 second startup delay +// (e.g. timer firing, or being pre-empted by data arriving) diff --git a/chrome/browser/geolocation/network_location_request.cc b/chrome/browser/geolocation/network_location_request.cc index 7fff715..65e3f37 100644 --- a/chrome/browser/geolocation/network_location_request.cc +++ b/chrome/browser/geolocation/network_location_request.cc @@ -66,7 +66,6 @@ NetworkLocationRequest::NetworkLocationRequest(URLRequestContextGetter* context, ListenerInterface* listener) : url_context_(context), timestamp_(kint64min), listener_(listener), url_(url), host_name_(host_name) { -// DCHECK(url_context_); DCHECK(listener); } @@ -89,7 +88,7 @@ bool NetworkLocationRequest::MakeRequest(const string16& access_token, timestamp_ = timestamp; url_fetcher_.reset(URLFetcher::Create( - host_name_.size(), // Used for testing + wifi_data.access_point_data.size(), // Used for testing url_, URLFetcher::POST, this)); url_fetcher_->set_upload_data(kMimeApplicationJson, post_body); url_fetcher_->set_request_context(url_context_); @@ -312,20 +311,31 @@ bool ParseServerResponse(const std::string& response_body, response_object->GetStringAsUTF16(kAccessTokenString, access_token); // Get the location - DictionaryValue* location_object; - if (!response_object->GetDictionary(kLocationString, &location_object)) { - Value* value = NULL; - // If the network provider was unable to provide a position fix, it should - // return a 200 with location == null. Otherwise it's an error. - return response_object->Get(kLocationString, &value) - && value && value->IsType(Value::TYPE_NULL); + Value* location_value = NULL; + if (!response_object->Get(kLocationString, &location_value)) { + LOG(INFO) << "ParseServerResponse() : Missing location attribute.\n"; + return false; + } + DCHECK(location_value); + + if (!location_value->IsType(Value::TYPE_DICTIONARY)) { + if (!location_value->IsType(Value::TYPE_NULL)) { + LOG(INFO) << "ParseServerResponse() : Unexpected location type" + << location_value->GetType() << ".\n"; + // If the network provider was unable to provide a position fix, it should + // return a HTTP 200, with "location" : null. Otherwise it's an error. + return false; + } + return true; // Successfully parsed response containing no fix. } - DCHECK(location_object); + DictionaryValue* location_object = + static_cast<DictionaryValue*>(location_value); // latitude and longitude fields are always required. double latitude, longitude; if (!GetAsDouble(*location_object, kLatitudeString, &latitude) || !GetAsDouble(*location_object, kLongitudeString, &longitude)) { + LOG(INFO) << "ParseServerResponse() : location lacks lat and/or long.\n"; return false; } // All error paths covered: now start actually modifying postion. |