Skip to content

Commit

Permalink
Add PortableIndex for tracking portable files (#2459)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryfu-msft authored Aug 27, 2022
1 parent f66b390 commit 1a6ca17
Show file tree
Hide file tree
Showing 24 changed files with 1,052 additions and 203 deletions.
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
3 changes: 3 additions & 0 deletions src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
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

0 comments on commit 1a6ca17

Please sign in to comment.