Skip to content

Commit

Permalink
Made Plugin Folder Unit tests & Expanding enviroment search (#6600)
Browse files Browse the repository at this point in the history
* Made Plugin Folder Unit tests. Fixes '>' not recursive searching (with max). Added that paths with an UnauthorizedAccessException are ignored. Added expanding enviroment search.

* Fixed some merging errors

* Added feedback from review

* Made the change that  ryanbodrug-microsoft suggested

* Stupid merge request... fixed

Co-authored-by: p-storm <paul.de.man@gmail.com>
  • Loading branch information
P-Storm and p-storm committed Oct 1, 2020
1 parent b2c00b1 commit 5c84de5
Show file tree
Hide file tree
Showing 31 changed files with 1,263 additions and 295 deletions.
1 change: 1 addition & 0 deletions .pipelines/ci/templates/build-powertoys-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ steps:
configuration: '$(BuildConfiguration)'
testSelector: 'testAssemblies'
testAssemblyVer2: |
**\Microsoft.Plugin.Folder.UnitTest.dll
**\Microsoft.Plugin.Program.UnitTests.dll
**\Microsoft.Plugin.Calculator.UnitTest.dll
**\Microsoft.Plugin.Uri.UnitTests.dll
Expand Down
7 changes: 7 additions & 0 deletions PowerToys.sln
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Setting
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Calculator.UnitTest", "src\modules\launcher\Plugins\Microsoft.Plugin.Calculator.UnitTest\Microsoft.Plugin.Calculator.UnitTest.csproj", "{632BBE62-5421-49EA-835A-7FFA4F499BD6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Folder.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.Folder.UnitTests\Microsoft.Plugin.Folder.UnitTests.csproj", "{4FA206A5-F69F-4193-BF8F-F6EEB496734C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Expand Down Expand Up @@ -537,6 +539,10 @@ Global
{632BBE62-5421-49EA-835A-7FFA4F499BD6}.Debug|x64.Build.0 = Debug|x64
{632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|x64.ActiveCfg = Release|x64
{632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|x64.Build.0 = Release|x64
{4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Debug|x64.ActiveCfg = Debug|x64
{4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Debug|x64.Build.0 = Debug|x64
{4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Release|x64.ActiveCfg = Release|x64
{4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -614,6 +620,7 @@ Global
{B81FB7B6-D30E-428F-908A-41422EFC1172} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{0F85E674-34AE-443D-954C-8321EB8B93B1} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
{632BBE62-5421-49EA-835A-7FFA4F499BD6} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{4FA206A5-F69F-4193-BF8F-F6EEB496734C} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using Microsoft.Plugin.Folder.Sources;
using Microsoft.Plugin.Folder.Sources.Result;
using Moq;
using NUnit.Framework;

namespace Microsoft.Plugin.Folder.UnitTests
{
public class DriveOrSharedFolderTests
{
[TestCase(@"\\test-server\testdir", true)]
[TestCase(@"c:", true)]
[TestCase(@"c:\", true)]
[TestCase(@"C:\", true)]
[TestCase(@"d:\", true)]
[TestCase(@"z:\", false)]
[TestCase(@"nope.exe", false)]
[TestCase(@"win32\test.dll", false)]
[TestCase(@"a\b\c\d", false)]
[TestCase(@"D", false)]
[TestCase(@"ZZ:\test", false)]
public void IsDriveOrSharedFolder_WhenCalled(string search, bool expectedSuccess)
{
// Setup
var driveInformationMock = new Mock<IDriveInformation>();

driveInformationMock.Setup(r => r.GetDriveNames())
.Returns(() => new[] { "c:", "d:" });

var folderLinksMock = new Mock<IFolderLinks>();
var folderHelper = new FolderHelper(driveInformationMock.Object, folderLinksMock.Object);

// Act
var isDriveOrSharedFolder = folderHelper.IsDriveOrSharedFolder(search);

// Assert
Assert.AreEqual(expectedSuccess, isDriveOrSharedFolder);
}

[TestCase('A', true)]
[TestCase('C', true)]
[TestCase('c', true)]
[TestCase('Z', true)]
[TestCase('z', true)]
[TestCase('ª', false)]
[TestCase('α', false)]
[TestCase('Ω', false)]
[TestCase('ɀ', false)]
public void ValidDriveLetter_WhenCalled(char letter, bool expectedSuccess)
{
// Setup
// Act
var isDriveOrSharedFolder = FolderHelper.ValidDriveLetter(letter);

// Assert
Assert.AreEqual(expectedSuccess, isDriveOrSharedFolder);
}

[TestCase("C:", true)]
[TestCase("C:\test", true)]
[TestCase("D:", false)]
[TestCase("INVALID", false)]
public void GenerateMaxFiles_WhenCalled(string search, bool hasValues)
{
// Setup
var folderHelperMock = new Mock<IFolderHelper>();
folderHelperMock.Setup(r => r.IsDriveOrSharedFolder(It.IsAny<string>()))
.Returns<string>(s => s.StartsWith("C:", StringComparison.CurrentCultureIgnoreCase));

var itemResultMock = new Mock<IItemResult>();

var internalDirectoryMock = new Mock<IQueryInternalDirectory>();
internalDirectoryMock.Setup(r => r.Query(It.IsAny<string>()))
.Returns(new List<IItemResult>() { itemResultMock.Object });

var processor = new InternalDirectoryProcessor(folderHelperMock.Object, internalDirectoryMock.Object);

// Act
var results = processor.Results(string.Empty, search);

// Assert
if (hasValues)
{
CollectionAssert.IsNotEmpty(results);
}
else
{
CollectionAssert.IsEmpty(results);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Plugin.Folder.Sources;
using Microsoft.Plugin.Folder.Sources.Result;
using Moq;
using NUnit.Framework;

namespace Microsoft.Plugin.Folder.UnitTests
{
[TestFixture]
public class InternalQueryFolderTests
{
private static readonly HashSet<string> DirectoryExist = new HashSet<string>()
{
@"c:",
@"c:\",
@"c:\Test\",
@"c:\Test\A\",
@"c:\Test\b\",
};

private static readonly HashSet<string> FilesExist = new HashSet<string>()
{
@"c:\bla.txt",
@"c:\Test\test.txt",
@"c:\Test\more-test.png",
};

private static Mock<IQueryFileSystemInfo> _queryFileSystemInfoMock;

[SetUp]
public void SetupMock()
{
var queryFileSystemInfoMock = new Mock<IQueryFileSystemInfo>();
queryFileSystemInfoMock.Setup(r => r.Exists(It.IsAny<string>()))
.Returns<string>(path => ContainsDirectory(path));

queryFileSystemInfoMock.Setup(r => r.MatchFileSystemInfo(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<SearchOption>()))
.Returns<string, string, SearchOption>(MatchFileSystemInfo);

_queryFileSystemInfoMock = queryFileSystemInfoMock;
}

// Windows supports C:\\\\\ => C:\
private static bool ContainsDirectory(string path)
{
return DirectoryExist.Contains(TrimDirectoryEnd(path));
}

private static string TrimDirectoryEnd(string path)
{
var trimEnd = path.TrimEnd('\\');

if (path.EndsWith('\\'))
{
trimEnd += '\\';
}

return trimEnd;
}

private static IEnumerable<DisplayFileInfo> MatchFileSystemInfo(string search, string incompleteName, SearchOption searchOption)
{
Func<string, bool> folderSearchFunc;
Func<string, bool> fileSearchFunc;
switch (searchOption)
{
case SearchOption.TopDirectoryOnly:
folderSearchFunc = s => s.Equals(search, StringComparison.CurrentCultureIgnoreCase);

var regexSearch = TrimDirectoryEnd(search);

fileSearchFunc = s => Regex.IsMatch(s, $"^{Regex.Escape(regexSearch)}[^\\\\]*$");
break;
case SearchOption.AllDirectories:
folderSearchFunc = s => s.StartsWith(search, StringComparison.CurrentCultureIgnoreCase);
fileSearchFunc = s => s.StartsWith(search, StringComparison.CurrentCultureIgnoreCase);
break;
default:
throw new ArgumentOutOfRangeException(nameof(searchOption), searchOption, null);
}

var directories = DirectoryExist.Where(s => folderSearchFunc(s))
.Select(dir => new DisplayFileInfo()
{
Type = DisplayType.Directory,
FullName = dir,
});

var files = FilesExist.Where(s => fileSearchFunc(s))
.Select(file => new DisplayFileInfo()
{
Type = DisplayType.File,
FullName = file,
});

return directories.Concat(files);
}

[Test]
public void Query_ThrowsException_WhenCalledNull()
{
// Setup
var queryInternalDirectory = new QueryInternalDirectory(new FolderSettings(), _queryFileSystemInfoMock.Object);

// Act & Assert
Assert.Throws<ArgumentNullException>(() => queryInternalDirectory.Query(null).ToArray());
}

[TestCase(@"c", 0, 0, false, Reason = "String empty is nothing")]
[TestCase(@"c:", 1, 1, false, Reason = "Root without \\")]
[TestCase(@"c:\", 1, 1, false, Reason = "Normal root")]
[TestCase(@"c:\Test", 1, 2, false, Reason = "Select yourself")]
[TestCase(@"c:\>", 2, 2, true, Reason = "Max Folder test recursive")]
[TestCase(@"c:\Test>", 2, 2, true, Reason = "2 Folders recursive")]
[TestCase(@"c:\not-exist", 1, 1, false, Reason = "Folder not exist, return root")]
[TestCase(@"c:\not-exist>", 2, 2, true, Reason = "Folder not exist, return root recursive")]
[TestCase(@"c:\not-exist\not-exist2", 0, 0, false, Reason = "Folder not exist, return root")]
[TestCase(@"c:\not-exist\not-exist2>", 0, 0, false, Reason = "Folder not exist, return root recursive")]
[TestCase(@"c:\bla.t", 1, 1, false, Reason = "Partial match file")]
public void Query_WhenCalled(string search, int folders, int files, bool truncated)
{
const int maxFolderSetting = 2;

// Setup
var folderSettings = new FolderSettings()
{
MaxFileResults = maxFolderSetting,
MaxFolderResults = maxFolderSetting,
};

var queryInternalDirectory = new QueryInternalDirectory(folderSettings, _queryFileSystemInfoMock.Object);

// Act
var isDriveOrSharedFolder = queryInternalDirectory.Query(search)
.ToLookup(r => r.GetType());

// Assert
Assert.AreEqual(files, isDriveOrSharedFolder[typeof(FileItemResult)].Count(), "File count doesn't match");
Assert.AreEqual(folders, isDriveOrSharedFolder[typeof(FolderItemResult)].Count(), "folder count doesn't match");

// Always check if there is less than max folders
Assert.LessOrEqual(isDriveOrSharedFolder[typeof(FileItemResult)].Count(), maxFolderSetting, "Files are not limited");
Assert.LessOrEqual(isDriveOrSharedFolder[typeof(FolderItemResult)].Count(), maxFolderSetting, "Folders are not limited");

// Checks if CreateOpenCurrentFolder is displayed
Assert.AreEqual(Math.Min(folders + files, 1), isDriveOrSharedFolder[typeof(CreateOpenCurrentFolderResult)].Count(), "CreateOpenCurrentFolder displaying is incorrect");

Assert.AreEqual(truncated, isDriveOrSharedFolder[typeof(TruncatedItemResult)].Count() == 1, "CreateOpenCurrentFolder displaying is incorrect");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>

<IsPackable>false</IsPackable>
<Platforms>x64</Platforms>
<RootNamespace>Microsoft.Plugin.Folder.UnitTests</RootNamespace>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Moq" Version="4.14.5" />
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Microsoft.Plugin.Folder\Microsoft.Plugin.Folder.csproj" />
</ItemGroup>


<ItemGroup>
<Compile Include="..\..\..\..\codeAnalysis\GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>
<AdditionalFiles Include="..\..\..\..\codeAnalysis\StyleCop.json">
<Link>StyleCop.json</Link>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers">
<Version>1.1.118</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using Microsoft.Plugin.Folder.Sources;
using Microsoft.Plugin.Folder.Sources.Result;
using Wox.Plugin;

namespace Microsoft.Plugin.Folder
{
internal interface IFolderProcessor
{
IEnumerable<IItemResult> Results(string actionKeyword, string search);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Linq;
using Microsoft.Plugin.Folder.Sources;
using Microsoft.Plugin.Folder.Sources.Result;
using Wox.Plugin;

namespace Microsoft.Plugin.Folder
{
public class InternalDirectoryProcessor : IFolderProcessor
{
private readonly IFolderHelper _folderHelper;
private readonly IQueryInternalDirectory _internalDirectory;

public InternalDirectoryProcessor(IFolderHelper folderHelper, IQueryInternalDirectory internalDirectory)
{
_folderHelper = folderHelper;
_internalDirectory = internalDirectory;
}

public IEnumerable<IItemResult> Results(string actionKeyword, string search)
{
if (!_folderHelper.IsDriveOrSharedFolder(search))
{
return Enumerable.Empty<IItemResult>();
}

return _internalDirectory.Query(search);
}
}
}

0 comments on commit 5c84de5

Please sign in to comment.