blob: 8e79f138171aa76a64089c3ce83ceb56f39c5e4f [file] [log] [blame] [edit]
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include <utility>
#include <vector>
#include "dawn/native/Blob.h"
#include "dawn/native/CacheRequest.h"
#include "dawn/tests/DawnNativeTest.h"
#include "dawn/tests/mocks/platform/CachingInterfaceMock.h"
namespace dawn::native {
namespace {
using ::testing::_;
using ::testing::ByMove;
using ::testing::Invoke;
using ::testing::MockFunction;
using ::testing::Return;
using ::testing::StrictMock;
using ::testing::WithArg;
class CacheRequestTests : public DawnNativeTest {
protected:
std::unique_ptr<dawn::platform::Platform> CreateTestPlatform() override {
return std::make_unique<DawnCachingMockPlatform>(&mMockCache);
}
DeviceBase* GetDevice() { return dawn::native::FromAPI(device.Get()); }
StrictMock<CachingInterfaceMock> mMockCache;
};
struct Foo {
int value;
};
#define REQUEST_MEMBERS(X) \
X(int, a) \
X(float, b) \
X(std::vector<unsigned int>, c) \
X(CacheKey::UnsafeUnkeyedValue<int*>, d) \
X(CacheKey::UnsafeUnkeyedValue<Foo>, e)
DAWN_MAKE_CACHE_REQUEST(CacheRequestForTesting, REQUEST_MEMBERS);
#undef REQUEST_MEMBERS
// static_assert the expected types for various return types from the cache hit handler and cache
// miss handler.
TEST_F(CacheRequestTests, CacheResultTypes) {
EXPECT_CALL(mMockCache, LoadData(_, _, nullptr, 0)).WillRepeatedly(Return(0));
// (int, ResultOrError<int>), should be ResultOrError<CacheResult<int>>.
auto v1 = LoadOrRun(
GetDevice(), CacheRequestForTesting{}, [](Blob) -> int { return 0; },
[](CacheRequestForTesting) -> ResultOrError<int> { return 1; });
v1.AcquireSuccess();
static_assert(std::is_same_v<ResultOrError<CacheResult<int>>, decltype(v1)>);
// (ResultOrError<float>, ResultOrError<float>), should be ResultOrError<CacheResult<float>>.
auto v2 = LoadOrRun(
GetDevice(), CacheRequestForTesting{}, [](Blob) -> ResultOrError<float> { return 0.0; },
[](CacheRequestForTesting) -> ResultOrError<float> { return 1.0; });
v2.AcquireSuccess();
static_assert(std::is_same_v<ResultOrError<CacheResult<float>>, decltype(v2)>);
}
// Test that using a CacheRequest builds a key from the device key, the request type enum, and all
// of the request members.
TEST_F(CacheRequestTests, MakesCacheKey) {
// Make a request.
CacheRequestForTesting req;
req.a = 1;
req.b = 0.2;
req.c = {3, 4, 5};
// Make the expected key.
CacheKey expectedKey;
StreamIn(&expectedKey, GetDevice()->GetCacheKey(), "CacheRequestForTesting", req.a, req.b,
req.c);
// Expect a call to LoadData with the expected key.
EXPECT_CALL(mMockCache, LoadData(_, expectedKey.size(), nullptr, 0))
.WillOnce(WithArg<0>(Invoke([&](const void* actualKeyData) {
EXPECT_EQ(memcmp(actualKeyData, expectedKey.data(), expectedKey.size()), 0);
return 0;
})));
// Load the request.
auto result = LoadOrRun(
GetDevice(), std::move(req), [](Blob) -> int { return 0; },
[](CacheRequestForTesting) -> ResultOrError<int> { return 0; })
.AcquireSuccess();
// The created cache key should be saved on the result.
EXPECT_EQ(result.GetCacheKey().size(), expectedKey.size());
EXPECT_EQ(memcmp(result.GetCacheKey().data(), expectedKey.data(), expectedKey.size()), 0);
}
// Test that members that are wrapped in UnsafeUnkeyedValue do not impact the key.
TEST_F(CacheRequestTests, CacheKeyIgnoresUnsafeIgnoredValue) {
// Make two requests with different UnsafeUnkeyedValues (UnsafeUnkeyed is declared on the struct
// definition).
int v1, v2;
CacheRequestForTesting req1;
req1.d = &v1;
req1.e = Foo{42};
CacheRequestForTesting req2;
req2.d = &v2;
req2.e = Foo{24};
EXPECT_CALL(mMockCache, LoadData(_, _, nullptr, 0)).WillOnce(Return(0)).WillOnce(Return(0));
static StrictMock<MockFunction<int(CacheRequestForTesting)>> cacheMissFn;
// Load the first request, and check that the unsafe unkeyed values were passed though
EXPECT_CALL(cacheMissFn, Call(_)).WillOnce(WithArg<0>(Invoke([&](CacheRequestForTesting req) {
EXPECT_EQ(req.d.UnsafeGetValue(), &v1);
EXPECT_FLOAT_EQ(req.e.UnsafeGetValue().value, 42);
return 0;
})));
auto r1 = LoadOrRun(
GetDevice(), std::move(req1), [](Blob) { return 0; },
[](CacheRequestForTesting req) -> ResultOrError<int> {
return cacheMissFn.Call(std::move(req));
})
.AcquireSuccess();
// Load the second request, and check that the unsafe unkeyed values were passed though
EXPECT_CALL(cacheMissFn, Call(_)).WillOnce(WithArg<0>(Invoke([&](CacheRequestForTesting req) {
EXPECT_EQ(req.d.UnsafeGetValue(), &v2);
EXPECT_FLOAT_EQ(req.e.UnsafeGetValue().value, 24);
return 0;
})));
auto r2 = LoadOrRun(
GetDevice(), std::move(req2), [](Blob) { return 0; },
[](CacheRequestForTesting req) -> ResultOrError<int> {
return cacheMissFn.Call(std::move(req));
})
.AcquireSuccess();
// Expect their keys to be the same.
EXPECT_EQ(r1.GetCacheKey().size(), r2.GetCacheKey().size());
EXPECT_EQ(memcmp(r1.GetCacheKey().data(), r2.GetCacheKey().data(), r1.GetCacheKey().size()), 0);
}
// Test the expected code path when there is a cache miss.
TEST_F(CacheRequestTests, CacheMiss) {
// Make a request.
CacheRequestForTesting req;
req.a = 1;
req.b = 0.2;
req.c = {3, 4, 5};
unsigned int* cPtr = req.c.data();
static StrictMock<MockFunction<int(Blob)>> cacheHitFn;
static StrictMock<MockFunction<int(CacheRequestForTesting)>> cacheMissFn;
// Mock a cache miss.
EXPECT_CALL(mMockCache, LoadData(_, _, nullptr, 0)).WillOnce(Return(0));
// Expect the cache miss, and return some value.
int rv = 42;
EXPECT_CALL(cacheMissFn, Call(_)).WillOnce(WithArg<0>(Invoke([=](CacheRequestForTesting req) {
// Expect the request contents to be the same. The data pointer for |c| is also the same
// since it was moved.
EXPECT_EQ(req.a, 1);
EXPECT_FLOAT_EQ(req.b, 0.2);
EXPECT_EQ(req.c.data(), cPtr);
return rv;
})));
// Load the request.
auto result = LoadOrRun(
GetDevice(), std::move(req),
[](Blob blob) -> int { return cacheHitFn.Call(std::move(blob)); },
[](CacheRequestForTesting req) -> ResultOrError<int> {
return cacheMissFn.Call(std::move(req));
})
.AcquireSuccess();
// Expect the result to store the value.
EXPECT_EQ(*result, rv);
EXPECT_FALSE(result.IsCached());
}
// Test the expected code path when there is a cache hit.
TEST_F(CacheRequestTests, CacheHit) {
// Make a request.
CacheRequestForTesting req;
req.a = 1;
req.b = 0.2;
req.c = {3, 4, 5};
static StrictMock<MockFunction<int(Blob)>> cacheHitFn;
static StrictMock<MockFunction<int(CacheRequestForTesting)>> cacheMissFn;
static constexpr char kCachedData[] = "hello world!";
// Mock a cache hit, and load the cached data.
EXPECT_CALL(mMockCache, LoadData(_, _, nullptr, 0)).WillOnce(Return(sizeof(kCachedData)));
EXPECT_CALL(mMockCache, LoadData(_, _, _, sizeof(kCachedData)))
.WillOnce(WithArg<2>(Invoke([](void* dataOut) {
memcpy(dataOut, kCachedData, sizeof(kCachedData));
return sizeof(kCachedData);
})));
// Expect the cache hit, and return some value.
int rv = 1337;
EXPECT_CALL(cacheHitFn, Call(_)).WillOnce(WithArg<0>(Invoke([=](Blob blob) {
// Expect the cached blob contents to match the cached data.
EXPECT_EQ(blob.Size(), sizeof(kCachedData));
EXPECT_EQ(memcmp(blob.Data(), kCachedData, sizeof(kCachedData)), 0);
return rv;
})));
// Load the request.
auto result = LoadOrRun(
GetDevice(), std::move(req),
[](Blob blob) -> int { return cacheHitFn.Call(std::move(blob)); },
[](CacheRequestForTesting req) -> ResultOrError<int> {
return cacheMissFn.Call(std::move(req));
})
.AcquireSuccess();
// Expect the result to store the value.
EXPECT_EQ(*result, rv);
EXPECT_TRUE(result.IsCached());
}
// Test the expected code path when there is a cache hit but the handler errors.
TEST_F(CacheRequestTests, CacheHitError) {
// Make a request.
CacheRequestForTesting req;
req.a = 1;
req.b = 0.2;
req.c = {3, 4, 5};
unsigned int* cPtr = req.c.data();
static StrictMock<MockFunction<ResultOrError<int>(Blob)>> cacheHitFn;
static StrictMock<MockFunction<int(CacheRequestForTesting)>> cacheMissFn;
static constexpr char kCachedData[] = "hello world!";
// Mock a cache hit, and load the cached data.
EXPECT_CALL(mMockCache, LoadData(_, _, nullptr, 0)).WillOnce(Return(sizeof(kCachedData)));
EXPECT_CALL(mMockCache, LoadData(_, _, _, sizeof(kCachedData)))
.WillOnce(WithArg<2>(Invoke([](void* dataOut) {
memcpy(dataOut, kCachedData, sizeof(kCachedData));
return sizeof(kCachedData);
})));
// Expect the cache hit.
EXPECT_CALL(cacheHitFn, Call(_)).WillOnce(WithArg<0>(Invoke([=](Blob blob) {
// Expect the cached blob contents to match the cached data.
EXPECT_EQ(blob.Size(), sizeof(kCachedData));
EXPECT_EQ(memcmp(blob.Data(), kCachedData, sizeof(kCachedData)), 0);
// Return an error.
return DAWN_VALIDATION_ERROR("fake test error");
})));
// Expect the cache miss handler since the cache hit errored.
int rv = 79;
EXPECT_CALL(cacheMissFn, Call(_)).WillOnce(WithArg<0>(Invoke([=](CacheRequestForTesting req) {
// Expect the request contents to be the same. The data pointer for |c| is also the same
// since it was moved.
EXPECT_EQ(req.a, 1);
EXPECT_FLOAT_EQ(req.b, 0.2);
EXPECT_EQ(req.c.data(), cPtr);
return rv;
})));
// Load the request.
auto result =
LoadOrRun(
GetDevice(), std::move(req),
[](Blob blob) -> ResultOrError<int> { return cacheHitFn.Call(std::move(blob)); },
[](CacheRequestForTesting req) -> ResultOrError<int> {
return cacheMissFn.Call(std::move(req));
})
.AcquireSuccess();
// Expect the result to store the value.
EXPECT_EQ(*result, rv);
EXPECT_FALSE(result.IsCached());
}
} // anonymous namespace
} // namespace dawn::native