| // Copyright 2020 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 "ui/gfx/x/property_cache.h" |
| |
| #include <memory> |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/x/x11_atom_cache.h" |
| #include "ui/gfx/x/xproto.h" |
| #include "ui/gfx/x/xproto_util.h" |
| |
| namespace x11 { |
| |
| class PropertyCacheTest : public testing::Test { |
| public: |
| ~PropertyCacheTest() override = default; |
| |
| protected: |
| base::flat_map<Atom, PropertyCache::PropertyValue>& GetProperties( |
| PropertyCache* property_cache) { |
| return property_cache->properties_; |
| } |
| |
| Connection* connection() { return connection_; } |
| |
| Window window() { return window_; } |
| |
| private: |
| void SetUp() override { |
| connection_ = Connection::Get(); |
| window_ = CreateDummyWindow(""); |
| } |
| |
| void TearDown() override { |
| connection_->DestroyWindow({window_}).Sync(); |
| window_ = Window::None; |
| connection_ = nullptr; |
| } |
| |
| Connection* connection_ = nullptr; |
| Window window_ = Window::None; |
| }; |
| |
| TEST_F(PropertyCacheTest, GetSync) { |
| auto atom = x11::GetAtom("DUMMY ATOM"); |
| SetProperty(window(), atom, Atom::CARDINAL, 1234); |
| |
| PropertyCache cache(connection(), window(), {atom}); |
| |
| // The cache should Sync() on getting the value. |
| EXPECT_FALSE(GetProperties(&cache)[atom].response.has_value()); |
| auto* value = cache.GetAs<uint32_t>(atom); |
| EXPECT_TRUE(GetProperties(&cache)[atom].response.has_value()); |
| |
| ASSERT_TRUE(value); |
| EXPECT_EQ(*value, 1234u); |
| } |
| |
| TEST_F(PropertyCacheTest, GetAsync) { |
| auto atom = x11::GetAtom("DUMMY ATOM"); |
| SetProperty(window(), atom, Atom::CARDINAL, 1234); |
| |
| PropertyCache cache(connection(), window(), {atom}); |
| |
| // The cache should not Sync() unnecessarily. |
| EXPECT_FALSE(GetProperties(&cache)[atom].response.has_value()); |
| connection()->Sync(); |
| connection()->DispatchAll(); |
| EXPECT_TRUE(GetProperties(&cache)[atom].response.has_value()); |
| |
| auto* value = cache.GetAs<uint32_t>(atom); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(*value, 1234u); |
| } |
| |
| TEST_F(PropertyCacheTest, Event) { |
| auto atom = x11::GetAtom("DUMMY ATOM"); |
| SetProperty(window(), atom, Atom::CARDINAL, 1234); |
| |
| PropertyCache cache(connection(), window(), {atom}); |
| |
| auto* value = cache.GetAs<uint32_t>(atom); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(*value, 1234u); |
| |
| // Change the property and sync to ensure the PropertyNotify event is ready to |
| // be dispatched. |
| SetProperty(window(), atom, Atom::CARDINAL, 5678); |
| connection()->Sync(); |
| connection()->ReadResponses(); |
| |
| // Dispatch the PropertyNotify event, which will cause the PropertyCache to |
| // send another GetPropertyRequest. Calling DispatchAll() would introduce a |
| // race condition where we could get the GetPropertyResponse early if the 2 |
| // round trips are completed fast enough. To avoid this, we use Dispatch(), |
| // which doesn't read or write on the socket. |
| while (connection()->Dispatch()) { |
| } |
| |
| // We don't have the new GetPropertyResponse yet, so the old value should |
| // still be there. |
| value = cache.GetAs<uint32_t>(atom); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(*value, 1234u); |
| |
| // Complete the second round trip to acquire the new property value. |
| connection()->Sync(); |
| connection()->DispatchAll(); |
| |
| // Now the cache should have the new value. |
| value = cache.GetAs<uint32_t>(atom); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(*value, 5678u); |
| } |
| |
| TEST_F(PropertyCacheTest, GetAs) { |
| auto atom = x11::GetAtom("DUMMY ATOM"); |
| SetProperty(window(), atom, Atom::CARDINAL, 1234); |
| |
| PropertyCache cache(connection(), window(), {atom}); |
| |
| // Get() should return the correct property value. |
| auto& response = cache.Get(atom); |
| ASSERT_TRUE(response); |
| EXPECT_EQ(response->bytes_after, 0u); |
| EXPECT_EQ(response->format, 32); |
| EXPECT_EQ(response->type, Atom::CARDINAL); |
| EXPECT_EQ(*response->value->front_as<uint32_t>(), 1234u); |
| EXPECT_EQ(response->value_len, 1u); |
| |
| // GetAs() should do the same thing as Get(). |
| size_t size = 0; |
| auto* value = cache.GetAs<uint32_t>(atom, &size); |
| EXPECT_EQ(size, 1u); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(*value, 1234u); |
| |
| // GetAs() should allow a nullptr size. |
| value = cache.GetAs<uint32_t>(atom /* size is defaulted to nullptr */); |
| ASSERT_TRUE(value); |
| EXPECT_EQ(*value, 1234u); |
| |
| // The below checks make requests and requires event dispatching, so |
| // synchronize all requests to avoid verbosity from Sync()ing everywhere. |
| connection()->SynchronizeForTest(true); |
| |
| // GetAs() should return nullptr if the type's size is mismatched. |
| SetProperty(window(), atom, Atom::CARDINAL, static_cast<uint8_t>(123)); |
| connection()->DispatchAll(); |
| value = cache.GetAs<uint32_t>(atom, &size); |
| EXPECT_EQ(size, 0u); |
| EXPECT_FALSE(value); |
| |
| // GetAs() should return nullptr if the property has no elements. |
| SetArrayProperty(window(), atom, Atom::CARDINAL, std::vector<uint32_t>()); |
| connection()->DispatchAll(); |
| value = cache.GetAs<uint32_t>(atom, &size); |
| EXPECT_EQ(size, 0u); |
| EXPECT_FALSE(value); |
| |
| // GetAs() should return nullptr if the property is deleted. |
| DeleteProperty(window(), atom); |
| connection()->DispatchAll(); |
| value = cache.GetAs<uint32_t>(atom, &size); |
| EXPECT_EQ(size, 0u); |
| EXPECT_FALSE(value); |
| |
| connection()->SynchronizeForTest(true); |
| } |
| |
| } // namespace x11 |