Files
qt-creator/tests/unit/unittest/storagecache-test.cpp
Marco Bubke 849b8cf000 QmlDesigner: Improve sqlite id
https://www.sqlite.org/autoinc.html says that "If no negative ROWID
values are inserted explicitly, then automatically generated ROWID
values will always be greater than zero."

So zero is an invalid value.

This changes reflect on it and makes it an invalid value too. Null is a
special value in SQL and it could cast to zero by accident. To prevent
that ambiguty we make zero an invalid value too.
You can explicit add negative rowids but that has size overhead because
positive values can be compressed. So explicit not positive values are
not supported.

Change-Id: I417ea9fec1573cfd9f1a98134f8adc567021988c
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
2023-05-09 13:42:31 +00:00

516 lines
16 KiB
C++

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "googletest.h"
#include "mockmutex.h"
#include "projectstoragemock.h"
#include "sqlitedatabasemock.h"
#include <projectstorage/storagecache.h>
#include <projectstorageids.h>
#include <utils/smallstringio.h>
namespace {
using QmlDesigner::SourceContextId;
using QmlDesigner::StorageCacheException;
using Utils::compare;
using Utils::reverseCompare;
class StorageAdapter
{
public:
auto fetchId(Utils::SmallStringView view) const { return storage.fetchSourceContextId(view); }
auto fetchValue(SourceContextId id) const { return storage.fetchSourceContextPath(id); }
auto fetchAll() const { return storage.fetchAllSourceContexts(); }
ProjectStorageMock &storage;
};
auto less(Utils::SmallStringView first, Utils::SmallStringView second) -> bool
{
return Utils::reverseCompare(first, second) < 0;
};
using CacheWithMockLocking = QmlDesigner::StorageCache<Utils::PathString,
Utils::SmallStringView,
SourceContextId,
StorageAdapter,
NiceMock<MockMutex>,
less,
QmlDesigner::Cache::SourceContext>;
using CacheWithoutLocking = QmlDesigner::StorageCache<Utils::PathString,
Utils::SmallStringView,
SourceContextId,
StorageAdapter,
NiceMock<MockMutexNonLocking>,
less,
QmlDesigner::Cache::SourceContext>;
template<typename Cache>
class StorageCache : public testing::Test
{
protected:
void SetUp()
{
std::sort(filePaths.begin(), filePaths.end(), [](auto &f, auto &l) {
return compare(f, l) < 0;
});
std::sort(reverseFilePaths.begin(), reverseFilePaths.end(), [](auto &f, auto &l) {
return reverseCompare(f, l) < 0;
});
ON_CALL(this->mockStorage, fetchSourceContextId(Eq("foo"))).WillByDefault(Return(id42));
ON_CALL(this->mockStorage, fetchSourceContextId(Eq("bar"))).WillByDefault(Return(id43));
ON_CALL(this->mockStorage, fetchSourceContextId(Eq("poo"))).WillByDefault(Return(id44));
ON_CALL(this->mockStorage, fetchSourceContextId(Eq("taa"))).WillByDefault(Return(id45));
ON_CALL(this->mockStorage, fetchSourceContextPath(this->id41))
.WillByDefault(Return(Utils::PathString("bar")));
ON_CALL(this->mockStorage, fetchSourceContextId(Eq(filePath1))).WillByDefault(Return(id1));
ON_CALL(this->mockStorage, fetchSourceContextId(Eq(filePath2))).WillByDefault(Return(id2));
ON_CALL(this->mockStorage, fetchSourceContextId(Eq(filePath3))).WillByDefault(Return(id3));
ON_CALL(this->mockStorage, fetchSourceContextId(Eq(filePath4))).WillByDefault(Return(id4));
ON_CALL(this->mockStorage, fetchSourceContextId(Eq(filePath5))).WillByDefault(Return(id5));
}
protected:
NiceMock<ProjectStorageMock> mockStorage;
StorageAdapter storageAdapter{mockStorage};
Cache cache{storageAdapter};
typename Cache::MutexType &mockMutex = cache.mutex();
Utils::PathString filePath1{"/file/pathOne"};
Utils::PathString filePath2{"/file/pathTwo"};
Utils::PathString filePath3{"/file/pathThree"};
Utils::PathString filePath4{"/file/pathFour"};
Utils::PathString filePath5{"/file/pathFife"};
Utils::PathStringVector filePaths{filePath1, filePath2, filePath3, filePath4, filePath5};
Utils::PathStringVector reverseFilePaths{filePath1, filePath2, filePath3, filePath4, filePath5};
SourceContextId id1{SourceContextId::create(1)};
SourceContextId id2{SourceContextId::create(2)};
SourceContextId id3{SourceContextId::create(3)};
SourceContextId id4{SourceContextId::create(4)};
SourceContextId id5{SourceContextId::create(5)};
SourceContextId id41{SourceContextId::create(41)};
SourceContextId id42{SourceContextId::create(42)};
SourceContextId id43{SourceContextId::create(43)};
SourceContextId id44{SourceContextId::create(44)};
SourceContextId id45{SourceContextId::create(45)};
};
using CacheTypes = ::testing::Types<CacheWithMockLocking, CacheWithoutLocking>;
TYPED_TEST_SUITE(StorageCache, CacheTypes);
TYPED_TEST(StorageCache, AddFilePath)
{
auto id = this->cache.id(this->filePath1);
ASSERT_THAT(id, this->id1);
}
TYPED_TEST(StorageCache, AddSecondFilePath)
{
this->cache.id(this->filePath1);
auto id = this->cache.id(this->filePath2);
ASSERT_THAT(id, this->id2);
}
TYPED_TEST(StorageCache, AddDuplicateFilePath)
{
this->cache.id(this->filePath1);
auto id = this->cache.id(this->filePath1);
ASSERT_THAT(id, this->id1);
}
TYPED_TEST(StorageCache, AddDuplicateFilePathBetweenOtherEntries)
{
this->cache.id(this->filePath1);
this->cache.id(this->filePath2);
this->cache.id(this->filePath3);
this->cache.id(this->filePath4);
auto id = this->cache.id(this->filePath3);
ASSERT_THAT(id, this->id3);
}
TYPED_TEST(StorageCache, GetFilePathForIdWithOneEntry)
{
this->cache.id(this->filePath1);
auto filePath = this->cache.value(this->id1);
ASSERT_THAT(filePath, this->filePath1);
}
TYPED_TEST(StorageCache, GetFilePathForIdWithSomeEntries)
{
this->cache.id(this->filePath1);
this->cache.id(this->filePath2);
this->cache.id(this->filePath3);
this->cache.id(this->filePath4);
auto filePath = this->cache.value(this->id3);
ASSERT_THAT(filePath, this->filePath3);
}
TYPED_TEST(StorageCache, GetAllFilePaths)
{
this->cache.id(this->filePath1);
this->cache.id(this->filePath2);
this->cache.id(this->filePath3);
this->cache.id(this->filePath4);
auto filePaths = this->cache.values({this->id1, this->id2, this->id3, this->id4});
ASSERT_THAT(filePaths,
ElementsAre(this->filePath1, this->filePath2, this->filePath3, this->filePath4));
}
TYPED_TEST(StorageCache, AddFilePaths)
{
auto ids = this->cache.ids({this->filePath1, this->filePath2, this->filePath3, this->filePath4});
ASSERT_THAT(ids, ElementsAre(this->id1, this->id2, this->id3, this->id4));
}
TYPED_TEST(StorageCache, AddFilePathsWithStorageFunction)
{
auto ids = this->cache.ids({"foo", "taa", "poo", "bar"});
ASSERT_THAT(ids, UnorderedElementsAre(this->id42, this->id43, this->id44, this->id45));
}
TYPED_TEST(StorageCache, IsEmpty)
{
auto isEmpty = this->cache.isEmpty();
ASSERT_TRUE(isEmpty);
}
TYPED_TEST(StorageCache, IsNotEmpty)
{
this->cache.id(this->filePath1);
auto isEmpty = this->cache.isEmpty();
ASSERT_FALSE(isEmpty);
}
TYPED_TEST(StorageCache, PopulateWithEmptyVector)
{
this->cache.uncheckedPopulate();
ASSERT_TRUE(this->cache.isEmpty());
}
TYPED_TEST(StorageCache, IsNotEmptyAfterPopulateWithSomeEntries)
{
typename TypeParam::CacheEntries entries{{this->filePath1.clone(), this->id1},
{this->filePath2.clone(), this->id4},
{this->filePath3.clone(), this->id3},
{this->filePath4.clone(), SourceContextId::create(5)}};
ON_CALL(this->mockStorage, fetchAllSourceContexts()).WillByDefault(Return(entries));
this->cache.uncheckedPopulate();
ASSERT_TRUE(!this->cache.isEmpty());
}
TYPED_TEST(StorageCache, GetEntryAfterPopulateWithSomeEntries)
{
typename TypeParam::CacheEntries entries{{this->filePath1.clone(), this->id1},
{this->filePath2.clone(), this->id2},
{this->filePath3.clone(), SourceContextId::create(7)},
{this->filePath4.clone(), this->id4}};
ON_CALL(this->mockStorage, fetchAllSourceContexts()).WillByDefault(Return(entries));
this->cache.uncheckedPopulate();
auto value = this->cache.value(SourceContextId::create(7));
ASSERT_THAT(value, this->filePath3);
}
TYPED_TEST(StorageCache, EntriesHaveUniqueIds)
{
typename TypeParam::CacheEntries entries{{this->filePath1.clone(), this->id1},
{this->filePath2.clone(), this->id2},
{this->filePath3.clone(), this->id3},
{this->filePath4.clone(), this->id3}};
ON_CALL(this->mockStorage, fetchAllSourceContexts()).WillByDefault(Return(entries));
ASSERT_THROW(this->cache.populate(), StorageCacheException);
}
TYPED_TEST(StorageCache, MultipleEntries)
{
typename TypeParam::CacheEntries entries{{this->filePath1.clone(), this->id1},
{this->filePath1.clone(), this->id2},
{this->filePath3.clone(), this->id3},
{this->filePath4.clone(), this->id4}};
ON_CALL(this->mockStorage, fetchAllSourceContexts()).WillByDefault(Return(entries));
ASSERT_THROW(this->cache.populate(), StorageCacheException);
}
TYPED_TEST(StorageCache, IdIsReadAndWriteLockedForUnknownEntry)
{
InSequence s;
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
EXPECT_CALL(this->mockMutex, lock());
EXPECT_CALL(this->mockMutex, unlock());
this->cache.id("foo");
}
TYPED_TEST(StorageCache, IdWithStorageFunctionIsReadAndWriteLockedForUnknownEntry)
{
InSequence s;
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
EXPECT_CALL(this->mockMutex, lock());
EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo")));
EXPECT_CALL(this->mockMutex, unlock());
this->cache.id("foo");
}
TYPED_TEST(StorageCache, IdWithStorageFunctionIsReadLockedForKnownEntry)
{
InSequence s;
this->cache.id("foo");
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
EXPECT_CALL(this->mockMutex, lock()).Times(0);
EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo"))).Times(0);
EXPECT_CALL(this->mockMutex, unlock()).Times(0);
this->cache.id("foo");
}
TYPED_TEST(StorageCache, IdIsReadLockedForKnownEntry)
{
this->cache.id("foo");
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
EXPECT_CALL(this->mockMutex, lock()).Times(0);
EXPECT_CALL(this->mockMutex, unlock()).Times(0);
this->cache.id("foo");
}
TYPED_TEST(StorageCache, IdsIsLocked)
{
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
this->cache.ids({"foo"});
}
TYPED_TEST(StorageCache, IdsWithStorageIsLocked)
{
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
this->cache.ids({"foo"});
}
TYPED_TEST(StorageCache, ValueIsLocked)
{
auto id = this->cache.id("foo");
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
this->cache.value(id);
}
TYPED_TEST(StorageCache, ValuesIsLocked)
{
auto ids = this->cache.ids({"foo", "bar"});
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
this->cache.values(ids);
}
TYPED_TEST(StorageCache, ValueWithStorageFunctionIsReadAndWriteLockedForUnknownId)
{
InSequence s;
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
EXPECT_CALL(this->mockMutex, lock());
EXPECT_CALL(this->mockStorage, fetchSourceContextPath(Eq(this->id41)));
EXPECT_CALL(this->mockMutex, unlock());
this->cache.value(this->id41);
}
TYPED_TEST(StorageCache, ValueWithStorageFunctionIsReadLockedForKnownId)
{
InSequence s;
this->cache.value(this->id41);
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
EXPECT_CALL(this->mockMutex, lock()).Times(0);
EXPECT_CALL(this->mockStorage, fetchSourceContextPath(Eq(this->id41))).Times(0);
EXPECT_CALL(this->mockMutex, unlock()).Times(0);
this->cache.value(this->id41);
}
TYPED_TEST(StorageCache, IdWithStorageFunctionWhichHasNoEntryIsCallingStorageFunction)
{
EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo")));
this->cache.id("foo");
}
TYPED_TEST(StorageCache, IdWithStorageFunctionWhichHasEntryIsNotCallingStorageFunction)
{
this->cache.id("foo");
EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo"))).Times(0);
this->cache.id("foo");
}
TYPED_TEST(StorageCache, IndexOfIdWithStorageFunctionWhichHasEntry)
{
this->cache.id("foo");
auto index = this->cache.id("foo");
ASSERT_THAT(index, this->id42);
}
TYPED_TEST(StorageCache, IndexOfIdWithStorageFunctionWhichHasNoEntry)
{
auto index = this->cache.id("foo");
ASSERT_THAT(index, this->id42);
}
TYPED_TEST(StorageCache, GetEntryByIndexAfterInsertingByCustomIndex)
{
auto index = this->cache.id("foo");
auto value = this->cache.value(index);
ASSERT_THAT(value, Eq("foo"));
}
TYPED_TEST(StorageCache, CallFetchSourceContextPathForLowerIndex)
{
auto index = this->cache.id("foo");
SourceContextId lowerIndex{SourceContextId::create(index.internalId() - 1)};
EXPECT_CALL(this->mockStorage, fetchSourceContextPath(Eq(lowerIndex)));
this->cache.value(lowerIndex);
}
TYPED_TEST(StorageCache, CallFetchSourceContextPathForUnknownIndex)
{
EXPECT_CALL(this->mockStorage, fetchSourceContextPath(Eq(this->id1)));
this->cache.value(this->id1);
}
TYPED_TEST(StorageCache, FetchSourceContextPathForUnknownIndex)
{
auto value = this->cache.value(this->id41);
ASSERT_THAT(value, Eq("bar"));
}
TYPED_TEST(StorageCache, AddCalls)
{
EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo")));
EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("bar")));
EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("poo")));
this->cache.add({"foo", "bar", "poo"});
}
TYPED_TEST(StorageCache, AddCallsOnlyForNewValues)
{
this->cache.add({"foo", "poo"});
EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("taa")));
EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("bar")));
this->cache.add({"foo", "bar", "poo", "taa"});
}
TYPED_TEST(StorageCache, GetIdAfterAddingValues)
{
this->cache.add({"foo", "bar", "poo", "taa"});
ASSERT_THAT(this->cache.value(this->cache.id("taa")), Eq("taa"));
}
TYPED_TEST(StorageCache, GetValueAfterAddingValues)
{
this->cache.add({"foo", "bar", "poo", "taa"});
ASSERT_THAT(this->cache.value(this->cache.id("taa")), Eq("taa"));
}
TYPED_TEST(StorageCache, GetIdAfterAddingValuesMultipleTimes)
{
this->cache.add({"foo", "taa"});
this->cache.add({"foo", "bar", "poo", "taa"});
ASSERT_THAT(this->cache.value(this->cache.id("taa")), Eq("taa"));
}
TYPED_TEST(StorageCache, GetIdAfterAddingTheSameValuesMultipleTimes)
{
this->cache.add({"foo", "taa", "poo", "taa", "bar", "taa"});
ASSERT_THAT(this->cache.value(this->cache.id("taa")), Eq("taa"));
}
TYPED_TEST(StorageCache, AddingEmptyValues)
{
this->cache.add({});
}
TYPED_TEST(StorageCache, FetchIdsFromStorageCalls)
{
InSequence s;
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
EXPECT_CALL(this->mockMutex, lock());
EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("foo")));
EXPECT_CALL(this->mockMutex, unlock());
EXPECT_CALL(this->mockMutex, lock_shared());
EXPECT_CALL(this->mockMutex, unlock_shared());
EXPECT_CALL(this->mockMutex, lock());
EXPECT_CALL(this->mockStorage, fetchSourceContextId(Eq("bar")));
EXPECT_CALL(this->mockMutex, unlock());
this->cache.ids({"foo", "bar"});
}
} // namespace