Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PortableIndex for tracking portable files #2459

Merged
merged 22 commits into from
Aug 27, 2022
13 changes: 13 additions & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ abi
ACCESSDENIED
ACTIONDATA
ACTIONSTART
addfile
addmanifest
addportablefile
addstore
admins
alloc
Expand Down Expand Up @@ -88,6 +90,7 @@ cpprestsdk
cppwinrt
CPRWL
createnew
createportabletable
createtables
cref
csproj
Expand Down Expand Up @@ -179,6 +182,7 @@ FILESINUSE
FILESUBTYPE
filesystem
FILETYPE
filetype
FILEVERSION
FLUSHEACHLINE
forcerestart
Expand Down Expand Up @@ -242,6 +246,7 @@ interop
INVALIDARG
iomanip
iostream
IPortable
ISAPPROVEDFOROUTPUT
isspace
istream
Expand Down Expand Up @@ -315,6 +320,7 @@ Nelon
netcoreapp
netstandard
newid
NOCASE
NOCLOSEPROCESS
nodiscard
noexcept
Expand Down Expand Up @@ -376,6 +382,7 @@ pipssource
PKCS
placeholders
png
portableindex
posix
powershell
pplx
Expand Down Expand Up @@ -416,7 +423,9 @@ REFCLSID
REFCOUNT
regex
regexp
removefile
removemanifest
removeportablefile
repolibtest
requeue
rescap
Expand Down Expand Up @@ -524,6 +533,7 @@ SWIPECONTROL
SYMED
symlink
symlinks
symlinktarget
Sys
sz
TARG
Expand All @@ -538,6 +548,7 @@ tempdb
terabyte
testcontainer
testmoniker
TESTPORTABLEFILE
Testrun
testsettingname
TEXTFORMAT
Expand Down Expand Up @@ -588,7 +599,9 @@ uninstalling
Unregister
Unregisters
untimes
updatefile
updatemanifest
updateportablefile
UPLEVEL
upvote
uregex
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@
<ClCompile Include="PackageCollection.cpp" />
<ClCompile Include="PackageTrackingCatalog.cpp" />
<ClCompile Include="PortableEntry.cpp" />
<ClCompile Include="PortableIndex.cpp" />
<ClCompile Include="PredefinedInstalledSource.cpp" />
<ClCompile Include="PreIndexedPackageSource.cpp" />
<ClCompile Include="Regex.cpp" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@
<ClCompile Include="PackageTrackingCatalog.cpp">
<Filter>Source Files\Repository</Filter>
</ClCompile>
<ClCompile Include="PortableIndex.cpp">
<Filter>Source Files\Repository</Filter>
</ClCompile>
<ClCompile Include="PredefinedInstalledSource.cpp">
<Filter>Source Files\Repository</Filter>
</ClCompile>
Expand Down
200 changes: 200 additions & 0 deletions src/AppInstallerCLITests/PortableIndex.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"
#include "TestCommon.h"
#include <SQLiteWrapper.h>
#include <Microsoft/SQLiteStorageBase.h>
#include <Microsoft/Schema/IPortableIndex.h>
#include <Microsoft/PortableIndex.h>
#include <Microsoft/Schema/Portable_1_0/PortableTable.h>

using namespace std::string_literals;
using namespace TestCommon;
using namespace AppInstaller::Repository::Microsoft;
using namespace AppInstaller::Repository::SQLite;
using namespace AppInstaller::Repository::Microsoft::Schema;

void CreateFakePortableFile(IPortableIndex::PortableFile& file)
{
file.SetFilePath("testPortableFile.exe");
file.FileType = IPortableIndex::PortableFileType::File;
file.SHA256 = "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b";
file.SymlinkTarget = "testSymlinkTarget.exe";
}

TEST_CASE("PortableIndexCreateLatestAndReopen", "[portableIndex]")
{
TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
INFO("Using temporary file named: " << tempFile.GetPath());

Schema::Version versionCreated;

// Create the index
{
PortableIndex index = PortableIndex::CreateNew(tempFile, Schema::Version::Latest());
versionCreated = index.GetVersion();
}

// Reopen the index for read only
{
INFO("Trying with Read");
PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read);
Schema::Version versionRead = index.GetVersion();
REQUIRE(versionRead == versionCreated);
}

// Reopen the index for read/write
{
INFO("Trying with ReadWrite");
PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
Schema::Version versionRead = index.GetVersion();
REQUIRE(versionRead == versionCreated);
}

// Reopen the index for immutable read
{
INFO("Trying with Immutable");
PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable);
Schema::Version versionRead = index.GetVersion();
REQUIRE(versionRead == versionCreated);
}
}

TEST_CASE("PortableIndexAddEntryToTable", "[portableIndex]")
{
TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
INFO("Using temporary file named: " << tempFile.GetPath());

IPortableIndex::PortableFile portableFile;
CreateFakePortableFile(portableFile);

{
PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 });
index.AddPortableFile(portableFile);
}

{
// Open it directly to directly test table state
Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
REQUIRE(!Schema::Portable_V1_0::PortableTable::IsEmpty(connection));
}

{
PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
index.RemovePortableFile(portableFile);
}

{
// Open it directly to directly test table state
Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
REQUIRE(Schema::Portable_V1_0::PortableTable::IsEmpty(connection));
}
}

TEST_CASE("PortableIndex_AddUpdateRemove", "[portableIndex]")
{
TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
INFO("Using temporary file named: " << tempFile.GetPath());

IPortableIndex::PortableFile portableFile;
CreateFakePortableFile(portableFile);

PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 });
index.AddPortableFile(portableFile);

// Apply changes to portable file
std::string updatedHash = "2db8ae7657c6622b04700137740002c51c36588e566651c9f67b4b096c8ad18b";
portableFile.FileType = IPortableIndex::PortableFileType::Symlink;
portableFile.SHA256 = updatedHash;
portableFile.SymlinkTarget = "fakeSymlinkTarget.exe";

REQUIRE(index.UpdatePortableFile(portableFile));

{
Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly);
auto fileFromIndex = Schema::Portable_V1_0::PortableTable::GetPortableFileById(connection, 1);
REQUIRE(fileFromIndex.has_value());
REQUIRE(fileFromIndex->GetFilePath() == "testPortableFile.exe");
REQUIRE(fileFromIndex->FileType == IPortableIndex::PortableFileType::Symlink);
REQUIRE(fileFromIndex->SHA256 == updatedHash);
REQUIRE(fileFromIndex->SymlinkTarget == "fakeSymlinkTarget.exe");
}

{
PortableIndex index2 = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
index2.RemovePortableFile(portableFile);
}

{
// Open it directly to directly test table state
Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
REQUIRE(Schema::Portable_V1_0::PortableTable::IsEmpty(connection));
}
}

TEST_CASE("PortableIndex_UpdateFile_CaseInsensitive", "[portableIndex]")
{
TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
INFO("Using temporary file named: " << tempFile.GetPath());

IPortableIndex::PortableFile portableFile;
CreateFakePortableFile(portableFile);

PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 });
index.AddPortableFile(portableFile);

// By default, portable file path is set to "testPortableFile.exe"
// Change file path to all upper case should still successfully update.
portableFile.SetFilePath("TESTPORTABLEFILE.exe");
std::string updatedHash = "2db8ae7657c6622b04700137740002c51c36588e566651c9f67b4b096c8ad18b";
portableFile.FileType = IPortableIndex::PortableFileType::Symlink;
portableFile.SHA256 = updatedHash;
portableFile.SymlinkTarget = "fakeSymlinkTarget.exe";

REQUIRE(index.UpdatePortableFile(portableFile));

{
Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly);
auto fileFromIndex = Schema::Portable_V1_0::PortableTable::GetPortableFileById(connection, 1);
REQUIRE(fileFromIndex.has_value());
REQUIRE(fileFromIndex->GetFilePath() == "TESTPORTABLEFILE.exe");
REQUIRE(fileFromIndex->FileType == IPortableIndex::PortableFileType::Symlink);
REQUIRE(fileFromIndex->SHA256 == updatedHash);
REQUIRE(fileFromIndex->SymlinkTarget == "fakeSymlinkTarget.exe");
}
}

TEST_CASE("PortableIndex_AddDuplicateFile", "[portableIndex]")
{
TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
INFO("Using temporary file named: " << tempFile.GetPath());

IPortableIndex::PortableFile portableFile;
CreateFakePortableFile(portableFile);

PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 });
index.AddPortableFile(portableFile);

// Change file path to all upper case. Adding duplicate file should fail.
portableFile.SetFilePath("TESTPORTABLEFILE.exe");
REQUIRE_THROWS(index.AddPortableFile(portableFile), ERROR_ALREADY_EXISTS);
}

TEST_CASE("PortableIndex_RemoveWithId", "[portableIndex]")
{
TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
INFO("Using temporary file named: " << tempFile.GetPath());

IPortableIndex::PortableFile portableFile;
CreateFakePortableFile(portableFile);

PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 });
index.AddPortableFile(portableFile);

{
Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
REQUIRE(Portable_V1_0::PortableTable::ExistsById(connection, 1));
Portable_V1_0::PortableTable::DeleteById(connection, 1);
REQUIRE_FALSE(Portable_V1_0::PortableTable::ExistsById(connection, 1));
}
}
Loading