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

Clean up git submodule tests and delay Git status runner #3800

Merged
merged 11 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/DevHome-CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ jobs:
- name: Tools UnitTests
if: ${{ matrix.platform != 'arm64' }}
run: |
foreach ($UnitTestPath in (Get-ChildItem "tools\\*\\*UnitTest\\bin\\${{ matrix.platform }}\\${{ matrix.configuration }}\\net8.0-windows10.0.22621.0\\*.UnitTest.dll")) {
foreach ($UnitTestPath in (Get-ChildItem "tools\\**\\*UnitTest\\bin\\${{ matrix.platform }}\\${{ matrix.configuration }}\\net8.0-windows10.0.22621.0\\*.UnitTest.dll")) {
cmd /c "$env:VSDevTestCmd" /Platform:${{ matrix.platform }} $UnitTestPath.FullName
}

- name: GitExtension UnitTests
if: ${{ matrix.platform != 'arm64' }}
run: cmd /c "$env:VSDevTestCmd" /Platform:${{ matrix.platform }} extensions\\GitExtension\\FileExplorerGitIntegration.UnitTest\\bin\\${{ matrix.platform }}\\${{ matrix.configuration }}\\net8.0-windows10.0.22621.0\\FileExplorerGitIntegration.UnitTest.dll
14 changes: 14 additions & 0 deletions Test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,20 @@ try {
& $vstestPath $winAppTestArgs
}
}

$vstestArgs = @(
"/Platform:$platform",
"/Logger:trx;LogFileName=FileExplorerGitIntegration.UnitTest-$platform-$configuration.trx",
"extensions\GitExtension\FileExplorerGitIntegration.UnitTest\bin\$platform\$configuration\net8.0-windows10.0.22621.0\FileExplorerGitIntegration.UnitTest.dll"
)
& $vstestPath $vstestArgs

$vstestArgs = @(
"/Platform:$platform",
"/Logger:trx;LogFileName=DevHome.FileExplorerSourceControlIntegrationUnitTest.UnitTest-$platform-$configuration.trx",
"tools\Customization\DevHome.FileExplorerSourceControlIntegrationUnitTest\bin\$platform\$configuration\net8.0-windows10.0.22621.0\DevHome.FileExplorerSourceControlIntegrationUnitTest.dll"
)
& $vstestPath $vstestArgs
}
}
} catch {
Expand Down
59 changes: 59 additions & 0 deletions common/Helpers/DirectoryHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.IO;
using Microsoft.Windows.DevHome.SDK;
using Serilog;

namespace DevHome.Common.Helpers;

public static class DirectoryHelper
{
private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(DirectoryHelper));

// Attempt to delete a directory with retries and an increasing backoff delay between retry attempts.
// This is useful when the directory may be temporarily in use by another process and the deletion may fail.
public static void DeleteDirectoryWithRetries(string directoryPath, bool recursive = true, int maxRetries = 3, int initialRetryDelayMs = 100, bool throwOnFailure = true)
{
ArgumentOutOfRangeException.ThrowIfNullOrEmpty(directoryPath);
ArgumentOutOfRangeException.ThrowIfNegative(maxRetries);
ArgumentOutOfRangeException.ThrowIfNegative(initialRetryDelayMs);

var retryDelay = initialRetryDelayMs;
for (var i = 0; i <= maxRetries; ++i)
{
try
{
if (Directory.Exists(directoryPath))
{
Directory.Delete(directoryPath, recursive);
}

return;
}
catch (Exception ex)
{
if (i == maxRetries)
{
_log.Error(ex, $"Failed to delete directory {directoryPath} on attempt {i + 1}.");
if (throwOnFailure)
{
throw;
}
else
{
return;
}
}
else
{
_log.Information(ex, $"Failed to delete directory {directoryPath} on attempt {i + 1}. Retrying up to {maxRetries - i} more times.");
}
}

System.Threading.Thread.Sleep(retryDelay);
retryDelay *= 2;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="MSTest.TestAdapter" Version="3.5.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.5.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FileExplorerGitIntegration\FileExplorerGitIntegration.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="resources\**\*.*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<None Remove="resources\resources_readme.txt" />
</ItemGroup>
<ItemGroup>
<Content Include="SandboxHelper.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.Diagnostics;
using DevHome.Common.Helpers;
using FileExplorerGitIntegration.Models;
using LibGit2Sharp;

Expand Down Expand Up @@ -65,7 +66,7 @@ public static void ClassCleanup()
dirInfo.Attributes = FileAttributes.Normal;
}

Directory.Delete(RepoPath, true);
DirectoryHelper.DeleteDirectoryWithRetries(RepoPath, true, 5, 100, false);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.Diagnostics;
using DevHome.Common.Helpers;
using FileExplorerGitIntegration.Models;
using LibGit2Sharp;

Expand Down Expand Up @@ -63,7 +64,7 @@ public static void ClassCleanup()
dirInfo.Attributes = FileAttributes.Normal;
}

Directory.Delete(RepoPath, true);
DirectoryHelper.DeleteDirectoryWithRetries(RepoPath, true, 5, 100, false);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,130 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using DevHome.Common.Helpers;
using FileExplorerGitIntegration.Models;
using LibGit2Sharp;
using Windows.Devices.Geolocation;

namespace FileExplorerGitIntegration.UnitTest;

[TestClass]
public class GitSubmoduleUnitTests
{
private const string RepoUrl = "https://github.com/libgit2/TestGitRepository.git";
private const string FolderStatusProp = "System.VersionControl.CurrentFolderStatus";
private const string StatusProp = "System.VersionControl.Status";
private const string ShaProp = "System.VersionControl.LastChangeID";

private static SandboxHelper? _sandbox;
private static GitLocalRepository? _repo;
private static string? _repoPath;

public enum CommitHashState
{
Base,
Previous,
New,
NewInSubmodule,
Missing,
}

private static readonly Dictionary<CommitHashState, string> _commits = [];

[ClassInitialize]
public static void ClassInitialize(TestContext testContext)
#pragma warning disable SA1313
public static void ClassInitialize(TestContext _)
#pragma warning restore SA1313
{
_sandbox = new();
var repoPath = _sandbox.CreateSandbox("submodules");
_sandbox.CreateSandbox("submodules_target");
_repo = new GitLocalRepository(repoPath);
_commits.Clear();
_repoPath = Directory.CreateTempSubdirectory("GitSubmoduleUnitTests").FullName;
Repository.Clone(RepoUrl, _repoPath);

GitDetect gitDetector = new();
gitDetector.DetectGit();
var gitPath = gitDetector.GitConfiguration.ReadInstallPath();

// Set identity for git commits
GitExecute.ExecuteGitCommand(gitPath, _repoPath, $"config user.email test@GitSubmoduleUnitTests");
GitExecute.ExecuteGitCommand(gitPath, _repoPath, $"config user.name Test GitSubmoduleUnitTests");

// Get the base and previous commit SHAs
{
var result = GitExecute.ExecuteGitCommand(gitPath, _repoPath, "log -n 2 --pretty=format:%H -- .");
Assert.AreEqual(Microsoft.Windows.DevHome.SDK.ProviderOperationStatus.Success, result.Status);
Assert.IsNotNull(result.Output);
var parts = result.Output.Split('\n');
Assert.AreEqual(2, parts.Length);
_commits[CommitHashState.Base] = parts[0];
_commits[CommitHashState.Previous] = parts[1];
Assert.AreNotEqual(_commits[CommitHashState.Base], _commits[CommitHashState.Previous]);
}

// Create a bunch of submodule baselines
GitExecute.ExecuteGitCommand(gitPath, _repoPath, $"submodule add -- {RepoUrl} sm_changed_file");
GitExecute.ExecuteGitCommand(gitPath, _repoPath, $"submodule add -- {RepoUrl} sm_changed_head");
GitExecute.ExecuteGitCommand(gitPath, _repoPath, $"submodule add -- {RepoUrl} sm_changed_index");
GitExecute.ExecuteGitCommand(gitPath, _repoPath, $"submodule add -- {RepoUrl} sm_changed_untracked_file");
GitExecute.ExecuteGitCommand(gitPath, _repoPath, $"submodule add -- {RepoUrl} sm_missing_commits");
GitExecute.ExecuteGitCommand(gitPath, _repoPath, $"submodule add -- {RepoUrl} sm_unchanged");
GitExecute.ExecuteGitCommand(gitPath, _repoPath, $"submodule add -- {RepoUrl} sm_unchanged_detached");
GitExecute.ExecuteGitCommand(gitPath, Path.Combine(_repoPath, "sm_unchanged_detached"), $"checkout {_commits[CommitHashState.Base]}");

// Commit and get the new SHA
GitExecute.ExecuteGitCommand(gitPath, _repoPath, "commit -m \"Adding submodules\"");
{
var result = GitExecute.ExecuteGitCommand(gitPath, _repoPath, "log -n 1 --pretty=format:%H -- .");
Assert.AreEqual(Microsoft.Windows.DevHome.SDK.ProviderOperationStatus.Success, result.Status);
Assert.IsNotNull(result.Output);
var parts = result.Output.Split('\n');
Assert.AreEqual(1, parts.Length);
_commits[CommitHashState.New] = parts[0];
Assert.AreNotEqual(_commits[CommitHashState.New], _commits[CommitHashState.Base]);
}

// Add and stage (but not commit) new submodule
GitExecute.ExecuteGitCommand(gitPath, _repoPath, $"submodule add -- {RepoUrl} sm_added_and_uncommitted");

// Modify submodules
File.AppendAllText(Path.Combine(_repoPath, "sm_changed_file/master.txt"), "In this submodule, the file is changed in the working directory.");
GitExecute.ExecuteGitCommand(gitPath, Path.Combine(_repoPath, "sm_changed_head"), $"config user.email test@GitSubmoduleUnitTests");
GitExecute.ExecuteGitCommand(gitPath, Path.Combine(_repoPath, "sm_changed_head"), $"config user.name Test GitSubmoduleUnitTests");
File.AppendAllText(Path.Combine(_repoPath, "sm_changed_head/master.txt"), "In this submodule, the file is changed and the change is committed to HEAD.");
GitExecute.ExecuteGitCommand(gitPath, Path.Combine(_repoPath, "sm_changed_head"), "commit --all --message \"Committing a change in the submodule.\"");
File.AppendAllText(Path.Combine(_repoPath, "sm_changed_index/master.txt"), "In this submodule, the file is changed and the change is committed to HEAD.");
GitExecute.ExecuteGitCommand(gitPath, Path.Combine(_repoPath, "sm_changed_index"), "stage --all");
File.AppendAllText(Path.Combine(_repoPath, "sm_changed_untracked_file/untracked_file.txt"), "In this submodule, we've added an untracked file.");
GitExecute.ExecuteGitCommand(gitPath, Path.Combine(_repoPath, "sm_missing_commits"), $"checkout {_commits[CommitHashState.Previous]}");
File.AppendAllLines(
Path.Combine(_repoPath, ".gitmodules"),
["[submodule \"sm_gitmodules_only\"]", "\tpath = sm_gitmodules_only", "\turl = ..\\\\submodules_target"]);

// Get the new commit SHA in sm_changed_head
{
var result = GitExecute.ExecuteGitCommand(gitPath, Path.Combine(_repoPath, "sm_changed_head"), "log -n 1 --pretty=format:%H -- .");
Assert.AreEqual(Microsoft.Windows.DevHome.SDK.ProviderOperationStatus.Success, result.Status);
Assert.IsNotNull(result.Output);
var parts = result.Output.Split('\n');
Assert.AreEqual(1, parts.Length);
_commits[CommitHashState.NewInSubmodule] = parts[0];
Assert.AreNotEqual(_commits[CommitHashState.NewInSubmodule], _commits[CommitHashState.Base]);
}

_repo = new GitLocalRepository(_repoPath);
}

[ClassCleanup]
public static void ClassCleanup()
{
if (_sandbox is not null)
_repo = null;
GC.Collect(2);
if (_repoPath is not null)
{
_sandbox.Cleanup();
_sandbox = null;
DirectoryHelper.DeleteDirectoryWithRetries(_repoPath, true, 5, 100, false);
}

_repo = null;
}

[TestMethod]
[DataRow("", FolderStatusProp, "Branch: main | +1 ~1 -0 | +0 ~7 -0")]
[DataRow("", FolderStatusProp, "Branch: master ↑1 | +1 ~1 -0 | +0 ~6 -0")]
[DataRow(".gitmodules", StatusProp, "Staged, Modified")]
[DataRow("README.txt", StatusProp, "")]
[DataRow("sm_added_and_uncommitted", StatusProp, "Submodule Added")]
Expand All @@ -46,7 +133,6 @@ public static void ClassCleanup()
[DataRow("sm_changed_index", StatusProp, "Submodule Dirty")]
[DataRow("sm_changed_untracked_file", StatusProp, "Submodule Dirty")]
[DataRow("sm_missing_commits", StatusProp, "Submodule Changed")]
[DataRow("sm_missing_commits_detached", StatusProp, "Submodule Changed")]
[DataRow("sm_unchanged", StatusProp, "")]
[DataRow("sm_unchanged_detached", StatusProp, "")]
public void RootFolderStatus(string path, string property, string value)
Expand All @@ -58,55 +144,53 @@ public void RootFolderStatus(string path, string property, string value)
}

[TestMethod]
[DataRow("", ShaProp, "8a303a1d530d9d4e9f31002d4c9d1d8f1cd78940")]
[DataRow(".gitmodules", ShaProp, "d8ebdc0b3c1d5240d4fc1c4cd3728ff561e714ad")]
[DataRow("README.txt", ShaProp, "74b157c3bfd2f24323c3bc6e5e96639a424f157f")]
[DataRow("sm_added_and_uncommitted", ShaProp, "")]
[DataRow("sm_changed_file", ShaProp, "8a303a1d530d9d4e9f31002d4c9d1d8f1cd78940")]
[DataRow("sm_changed_head", ShaProp, "8a303a1d530d9d4e9f31002d4c9d1d8f1cd78940")]
[DataRow("sm_changed_index", ShaProp, "8a303a1d530d9d4e9f31002d4c9d1d8f1cd78940")]
[DataRow("sm_changed_untracked_file", ShaProp, "8a303a1d530d9d4e9f31002d4c9d1d8f1cd78940")]
[DataRow("sm_missing_commits", ShaProp, "8a303a1d530d9d4e9f31002d4c9d1d8f1cd78940")]
[DataRow("sm_missing_commits_detached", ShaProp, "8a303a1d530d9d4e9f31002d4c9d1d8f1cd78940")]
[DataRow("sm_unchanged", ShaProp, "8a303a1d530d9d4e9f31002d4c9d1d8f1cd78940")]
[DataRow("sm_unchanged_detached", ShaProp, "8a303a1d530d9d4e9f31002d4c9d1d8f1cd78940")]
public void RootFolderCommit(string path, string property, string value)
[DataRow("", ShaProp, CommitHashState.New)]
[DataRow(".gitmodules", ShaProp, CommitHashState.New)]
[DataRow("sm_added_and_uncommitted", ShaProp, CommitHashState.Missing)]
[DataRow("sm_changed_file", ShaProp, CommitHashState.New)]
[DataRow("sm_changed_head", ShaProp, CommitHashState.New)]
[DataRow("sm_changed_index", ShaProp, CommitHashState.New)]
[DataRow("sm_changed_untracked_file", ShaProp, CommitHashState.New)]
[DataRow("sm_missing_commits", ShaProp, CommitHashState.New)]
[DataRow("sm_unchanged", ShaProp, CommitHashState.New)]
[DataRow("sm_unchanged_detached", ShaProp, CommitHashState.New)]
public void RootFolderCommit(string path, string property, CommitHashState state)
{
Assert.IsNotNull(_repo);
var result = _repo.GetProperties([property], path);
Assert.IsNotNull(result);
if (result.TryGetValue(property, out var actual))
{
Assert.AreEqual(value, actual);
Assert.AreEqual(_commits[state], actual);
}
else
{
Assert.AreEqual(value, string.Empty);
Assert.AreEqual(CommitHashState.Missing, state);
}
}

[TestMethod]
[DataRow("sm_added_and_uncommitted\\file_to_modify", ShaProp, "e9a899083a7e2b25d7a41e69463ce083bf9ef6ef")]
[DataRow("sm_changed_file\\file_to_modify", ShaProp, "e9a899083a7e2b25d7a41e69463ce083bf9ef6ef")]
[DataRow("sm_changed_head\\file_to_modify", ShaProp, "2ab664114c928551863c33d694965c79b6b75144")]
[DataRow("sm_changed_index\\file_to_modify", ShaProp, "e9a899083a7e2b25d7a41e69463ce083bf9ef6ef")]
[DataRow("sm_changed_untracked_file\\file_to_modify", ShaProp, "e9a899083a7e2b25d7a41e69463ce083bf9ef6ef")]
[DataRow("sm_missing_commits\\file_to_modify", ShaProp, "8e623bcf5aeceb8af7c0f0b22b82322f6c82fd4b")]
[DataRow("sm_missing_commits_detached\\file_to_modify", ShaProp, "8e623bcf5aeceb8af7c0f0b22b82322f6c82fd4b")]
[DataRow("sm_unchanged\\file_to_modify", ShaProp, "e9a899083a7e2b25d7a41e69463ce083bf9ef6ef")]
[DataRow("sm_unchanged_detached\\file_to_modify", ShaProp, "e9a899083a7e2b25d7a41e69463ce083bf9ef6ef")]
public void SubmoduleFilesCommit(string path, string property, string value)
[DataRow("sm_added_and_uncommitted\\.", ShaProp, CommitHashState.Base)]
[DataRow("sm_changed_file\\.", ShaProp, CommitHashState.Base)]
[DataRow("sm_changed_head\\.", ShaProp, CommitHashState.NewInSubmodule)]
[DataRow("sm_changed_head\\master.txt", ShaProp, CommitHashState.NewInSubmodule)]
[DataRow("sm_changed_index\\.", ShaProp, CommitHashState.Base)]
[DataRow("sm_changed_untracked_file\\.", ShaProp, CommitHashState.Base)]
[DataRow("sm_missing_commits\\.", ShaProp, CommitHashState.Previous)]
[DataRow("sm_unchanged\\.", ShaProp, CommitHashState.Base)]
[DataRow("sm_unchanged_detached\\.", ShaProp, CommitHashState.Base)]
public void SubmoduleFilesCommit(string path, string property, CommitHashState state)
{
Assert.IsNotNull(_repo);
var result = _repo.GetProperties([property], path);
Assert.IsNotNull(result);
if (result.TryGetValue(property, out var actual))
{
Assert.AreEqual(value, actual);
Assert.AreEqual(_commits[state], actual);
}
else
{
Assert.AreEqual(value, string.Empty);
Assert.AreEqual(CommitHashState.Missing, state);
}
}
}
Loading
Loading