From 7428df46946c03ce77de485a64f2a7ec900c21f7 Mon Sep 17 00:00:00 2001 From: AliReza Date: Thu, 30 May 2024 01:52:19 +0200 Subject: [PATCH 1/7] test: add EchoWithInclude test --- tests/HuskyIntegrationTests/Issue106Tests.cs | 61 ++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/HuskyIntegrationTests/Issue106Tests.cs diff --git a/tests/HuskyIntegrationTests/Issue106Tests.cs b/tests/HuskyIntegrationTests/Issue106Tests.cs new file mode 100644 index 0000000..ee3e86d --- /dev/null +++ b/tests/HuskyIntegrationTests/Issue106Tests.cs @@ -0,0 +1,61 @@ +using System.Runtime.CompilerServices; +using DotNet.Testcontainers.Containers; +using FluentAssertions; + +namespace HuskyIntegrationTests; +[Collection("docker fixture")] +public class Issue106Tests (DockerFixture docker, ITestOutputHelper output) : IClassFixture +{ + + [Fact] + public async Task EchoWithIncludeTask_ShouldSkip_WhenNoMatchFilesFound() + { + // arrange + var c = await ArrangeContainer(); + await c.BashAsync("git add ."); + + // act + var result = await c.BashAsync(output, "git commit -m 'add task-runner.json'"); + + // assert + result.ExitCode.Should().Be(0); + result.Stderr.Should().Contain(Extensions.Skipped); + } + + private async Task ArrangeContainer([CallerMemberName] string name = null!) + { + var c = await docker.StartWithInstalledHusky(name); + await c.BashAsync("dotnet tool restore"); + await c.BashAsync("git add ."); + + const string tasks = + """ + { + "tasks": [ + { + "name": "EchoWithInclude", + "group": "pre-commit", + "command": "bash", + "args": [ + "-c", + "echo Husky.Net is awesome!" + ], + "windows": { + "command": "cmd", + "args": [ + "/c", + "echo Husky.Net is awesome!" + ] + }, + "include": [ + "client/**/*" + ] + } + ] + } + """; + await c.UpdateTaskRunner(tasks); + await c.BashAsync("dotnet husky add pre-commit -c 'dotnet husky run -g pre-commit'"); + return c; + } +} From 76b974deefad5423c26445647643b02c754e4117 Mon Sep 17 00:00:00 2001 From: AliReZa Sabouri Date: Sun, 9 Jun 2024 03:43:24 +0200 Subject: [PATCH 2/7] test: improve integration test speed --- .../HuskyIntegrationTests.csproj | 8 ++ tests/HuskyIntegrationTests/Issue106Tests.cs | 12 +- tests/HuskyIntegrationTests/Issue99Tests.cs | 30 ++--- tests/HuskyIntegrationTests/Tests.cs | 57 +++++----- .../Utilities/DockerFixture.cs | 104 ------------------ .../Utilities/DockerFixtureCollection.cs | 7 -- .../Utilities/DockerHelper.cs | 92 ++++++++++++++++ .../Utilities/DockerLogger.cs | 30 ----- .../Utilities/Extensions.cs | 41 ------- .../Utilities/GlobalImageBuilder.cs | 63 +++++++++++ tests/HuskyIntegrationTests/xunit.runner.json | 4 + 11 files changed, 216 insertions(+), 232 deletions(-) delete mode 100644 tests/HuskyIntegrationTests/Utilities/DockerFixture.cs delete mode 100644 tests/HuskyIntegrationTests/Utilities/DockerFixtureCollection.cs create mode 100644 tests/HuskyIntegrationTests/Utilities/DockerHelper.cs delete mode 100644 tests/HuskyIntegrationTests/Utilities/DockerLogger.cs delete mode 100644 tests/HuskyIntegrationTests/Utilities/Extensions.cs create mode 100644 tests/HuskyIntegrationTests/Utilities/GlobalImageBuilder.cs create mode 100644 tests/HuskyIntegrationTests/xunit.runner.json diff --git a/tests/HuskyIntegrationTests/HuskyIntegrationTests.csproj b/tests/HuskyIntegrationTests/HuskyIntegrationTests.csproj index 20e4701..f393df5 100644 --- a/tests/HuskyIntegrationTests/HuskyIntegrationTests.csproj +++ b/tests/HuskyIntegrationTests/HuskyIntegrationTests.csproj @@ -10,7 +10,9 @@ + + @@ -27,5 +29,11 @@ + + + + PreserveNewest + + diff --git a/tests/HuskyIntegrationTests/Issue106Tests.cs b/tests/HuskyIntegrationTests/Issue106Tests.cs index ee3e86d..a3b6ee7 100644 --- a/tests/HuskyIntegrationTests/Issue106Tests.cs +++ b/tests/HuskyIntegrationTests/Issue106Tests.cs @@ -3,15 +3,13 @@ using FluentAssertions; namespace HuskyIntegrationTests; -[Collection("docker fixture")] -public class Issue106Tests (DockerFixture docker, ITestOutputHelper output) : IClassFixture +public class Issue106Tests (ITestOutputHelper output) { - [Fact] - public async Task EchoWithIncludeTask_ShouldSkip_WhenNoMatchFilesFound() + public async Task EchoWithIncludeTask_WhenNoMatchFilesFound_ShouldSkip() { // arrange - var c = await ArrangeContainer(); + await using var c = await ArrangeContainer(); await c.BashAsync("git add ."); // act @@ -19,12 +17,12 @@ public async Task EchoWithIncludeTask_ShouldSkip_WhenNoMatchFilesFound() // assert result.ExitCode.Should().Be(0); - result.Stderr.Should().Contain(Extensions.Skipped); + result.Stderr.Should().Contain(DockerHelper.Skipped); } private async Task ArrangeContainer([CallerMemberName] string name = null!) { - var c = await docker.StartWithInstalledHusky(name); + var c = await DockerHelper.StartWithInstalledHusky(name); await c.BashAsync("dotnet tool restore"); await c.BashAsync("git add ."); diff --git a/tests/HuskyIntegrationTests/Issue99Tests.cs b/tests/HuskyIntegrationTests/Issue99Tests.cs index b51b8ff..02da342 100644 --- a/tests/HuskyIntegrationTests/Issue99Tests.cs +++ b/tests/HuskyIntegrationTests/Issue99Tests.cs @@ -4,14 +4,13 @@ namespace HuskyIntegrationTests; -[Collection("docker fixture")] -public class Issue99Tests(DockerFixture docker, ITestOutputHelper output) : IClassFixture +public class Issue99Tests(ITestOutputHelper output) { [Fact] public async Task StagedFiles_ShouldPassToJbCleanup_WithASemicolonSeparator() { // arrange - var c = await ArrangeContainer(); + await using var c = await ArrangeContainer(); // add 4 c# files for (var i = 2; i <= 4; i++) @@ -28,19 +27,19 @@ public static void TestMethod() { } await c.BashAsync("git add ."); -// act + // act var result = await c.BashAsync(output, "git commit -m 'add 4 new csharp classes'"); -// assert + // assert result.ExitCode.Should().Be(0); - result.Stderr.Should().Contain(Extensions.SuccessfullyExecuted); + result.Stderr.Should().Contain(DockerHelper.SuccessfullyExecuted); } [Fact] public async Task StagedFiles_ShouldSkip_WhenNoMatchFilesFound() { // arrange - var c = await ArrangeContainer(); + await using var c = await ArrangeContainer(installJetBrains: false); await c.BashAsync("git add ."); // act @@ -48,16 +47,19 @@ public async Task StagedFiles_ShouldSkip_WhenNoMatchFilesFound() // assert result.ExitCode.Should().Be(0); - result.Stderr.Should().Contain(Extensions.Skipped); + result.Stderr.Should().Contain(DockerHelper.Skipped); } - private async Task ArrangeContainer([CallerMemberName] string name = null!) + private async Task ArrangeContainer([CallerMemberName] string name = null!, bool installJetBrains = true) { - var c = await docker.StartWithInstalledHusky(name); - await c.BashAsync("dotnet tool install JetBrains.ReSharper.GlobalTools"); - await c.BashAsync("dotnet tool restore"); - await c.BashAsync("git add ."); - await c.BashAsync("git commit -m 'add jb tool'"); + var c = await DockerHelper.StartWithInstalledHusky(name); + if (installJetBrains) + { + await c.BashAsync("dotnet tool install JetBrains.ReSharper.GlobalTools --version 2024.1.3"); + await c.BashAsync("dotnet tool restore"); + await c.BashAsync("git add ."); + await c.BashAsync("git commit -m 'add jb tool'"); + } const string tasks = """ diff --git a/tests/HuskyIntegrationTests/Tests.cs b/tests/HuskyIntegrationTests/Tests.cs index 7fb2ed7..069aa3d 100644 --- a/tests/HuskyIntegrationTests/Tests.cs +++ b/tests/HuskyIntegrationTests/Tests.cs @@ -2,39 +2,38 @@ namespace HuskyIntegrationTests; -[Collection("docker fixture")] -public class Tests(DockerFixture docker, ITestOutputHelper output) +public class Tests(ITestOutputHelper output) { [Fact] -public async Task IntegrationTestCopyFolder() -{ - var c = await docker.CopyAndStartAsync(nameof(TestProjectBase)); - - await c.BashAsync("git init"); - await c.BashAsync("dotnet new tool-manifest"); - await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky"); - await c.BashAsync("dotnet tool restore"); - await c.BashAsync("dotnet husky install"); - var result = await c.BashAsync(output, "dotnet husky run"); + public async Task IntegrationTestCopyFolder() + { + await using var c = await DockerHelper.StartContainerAsync(nameof(TestProjectBase)); - result.Stdout.Should().Contain(Extensions.SuccessfullyExecuted); - result.ExitCode.Should().Be(0); -} + await c.BashAsync("git init"); + await c.BashAsync("dotnet new tool-manifest"); + await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky"); + await c.BashAsync("dotnet tool restore"); + await c.BashAsync("dotnet husky install"); + var result = await c.BashAsync(output, "dotnet husky run"); -[Fact] -public async Task IntegrationTest() -{ - var c = await docker.StartAsync(); + result.Stdout.Should().Contain(DockerHelper.SuccessfullyExecuted); + result.ExitCode.Should().Be(0); + } - await c.BashAsync("dotnet new classlib"); - await c.BashAsync("dotnet new tool-manifest"); - await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky"); - await c.BashAsync("git init"); - await c.BashAsync("dotnet husky install"); - var result = await c.BashAsync(output, "dotnet husky run"); - - result.Stdout.Should().Contain(Extensions.SuccessfullyExecuted); - result.ExitCode.Should().Be(0); -} + [Fact] + public async Task IntegrationTest() + { + await using var c = await DockerHelper.StartContainerAsync(); + + await c.BashAsync("dotnet new classlib"); + await c.BashAsync("dotnet new tool-manifest"); + await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky"); + await c.BashAsync("git init"); + await c.BashAsync("dotnet husky install"); + var result = await c.BashAsync(output, "dotnet husky run"); + + result.Stdout.Should().Contain(DockerHelper.SuccessfullyExecuted); + result.ExitCode.Should().Be(0); + } } diff --git a/tests/HuskyIntegrationTests/Utilities/DockerFixture.cs b/tests/HuskyIntegrationTests/Utilities/DockerFixture.cs deleted file mode 100644 index 8d775a5..0000000 --- a/tests/HuskyIntegrationTests/Utilities/DockerFixture.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Runtime.CompilerServices; -using DotNet.Testcontainers.Builders; -using DotNet.Testcontainers.Containers; -using DotNet.Testcontainers.Images; -using HuskyIntegrationTests; - -public class DockerFixture : IAsyncDisposable -{ - public IFutureDockerImage? Image { get; set; } - - private void BuildImage() - { - Image = new ImageFromDockerfileBuilder() - .WithBuildArgument("RESOURCE_REAPER_SESSION_ID", ResourceReaper.DefaultSessionId.ToString("D")) - .WithDockerfileDirectory(CommonDirectoryPath.GetSolutionDirectory(), string.Empty) - .WithDockerfile("Dockerfile") - .WithName("husky") - .WithCleanUp(false) - .Build(); - - Image.CreateAsync().GetAwaiter().GetResult(); - } - - public async Task CopyAndStartAsync(string folderNameToCopy, [CallerMemberName] string name = null!) - { - var container = new ContainerBuilder() - .WithResourceMapping(GetTestFolderPath(folderNameToCopy), "/test/") - .WithName(GenerateContainerName(name)) - .WithImage("husky") - .WithWorkingDirectory("/test/") - .WithEntrypoint("/bin/bash", "-c") - .WithCommand("tail -f /dev/null") - .WithImagePullPolicy(response => - { - if (response == null) - { - BuildImage(); - } - - return false; - }) - .Build(); - - await container.StartAsync(); - return container; - } - - public async Task StartAsync([CallerMemberName] string name = null!) - { - var container = new ContainerBuilder() - .WithName(GenerateContainerName(name)) - .WithImage("husky") - .WithWorkingDirectory("/test/") - .WithEntrypoint("/bin/bash", "-c") - .WithCommand("tail -f /dev/null") - .WithImagePullPolicy(response => - { - if (response == null) - { - BuildImage(); - } - - return false; - }) - .Build(); - - await container.StartAsync(); - return container; - } - - - public async Task StartWithInstalledHusky([CallerMemberName] string name = null!) - { - var c = await CopyAndStartAsync(nameof(TestProjectBase), name); - await c.BashAsync("git init"); - await c.BashAsync("dotnet new tool-manifest"); - await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky"); - await c.BashAsync("dotnet tool restore"); - await c.BashAsync("dotnet husky install"); - await c.BashAsync("git config --global user.email \"you@example.com\""); - await c.BashAsync("git config --global user.name \"Your Name\""); - await c.BashAsync("git add ."); - await c.BashAsync("git commit -m 'initial commit'"); - return c; - } - - - private static string GenerateContainerName(string name) - { - return $"{name}-{Guid.NewGuid().ToString("N")[..4]}"; - } - - - private static string GetTestFolderPath(string folderName) - { - var baseDirectory = CommonDirectoryPath.GetProjectDirectory().DirectoryPath; - return Path.Combine(baseDirectory, folderName); - } - - public ValueTask DisposeAsync() - { - return Image?.DisposeAsync() ?? ValueTask.CompletedTask; - } -} diff --git a/tests/HuskyIntegrationTests/Utilities/DockerFixtureCollection.cs b/tests/HuskyIntegrationTests/Utilities/DockerFixtureCollection.cs deleted file mode 100644 index 399365b..0000000 --- a/tests/HuskyIntegrationTests/Utilities/DockerFixtureCollection.cs +++ /dev/null @@ -1,7 +0,0 @@ -[CollectionDefinition("docker fixture")] -public class DockerFixtureCollection : ICollectionFixture -{ - // This class has no code, and is never created. Its purpose is simply - // to be the place to apply [CollectionDefinition] and all the - // ICollectionFixture<> interfaces. -} diff --git a/tests/HuskyIntegrationTests/Utilities/DockerHelper.cs b/tests/HuskyIntegrationTests/Utilities/DockerHelper.cs new file mode 100644 index 0000000..2e5d467 --- /dev/null +++ b/tests/HuskyIntegrationTests/Utilities/DockerHelper.cs @@ -0,0 +1,92 @@ +using System.Runtime.CompilerServices; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; +using HuskyIntegrationTests.Utilities; + +namespace HuskyIntegrationTests; + +public static class DockerHelper +{ + public const string SuccessfullyExecuted = "✔ Successfully executed"; + public const string Skipped = "💤 Skipped, no matched files"; + + public static async Task BashAsync(this IContainer container, params string[] command) + { + var result = await container.ExecAsync(["/bin/bash", "-c", ..command]); + if (result.ExitCode != 0) + throw new Exception(result.Stderr + result.Stdout); + return result; + } + + public static async Task BashAsync(this IContainer container, ITestOutputHelper output, params string[] command) + { + var result = await container.ExecAsync(["/bin/bash", "-c", ..command]); + output.WriteLine($"{string.Join(" ", command)}:"); + + if (!string.IsNullOrEmpty(result.Stdout)) + output.WriteLine(result.Stdout); + + if (!string.IsNullOrEmpty(result.Stderr)) + output.WriteLine(result.Stderr); + + return result; + } + + public static Task UpdateTaskRunner(this IContainer container, string content) + { + return container.BashAsync($"echo -e '{content}' > /test/.husky/task-runner.json"); + } + + public static Task AddCsharpClass(this IContainer container, string content, string fileName = "Class2.cs") + { + return container.BashAsync($"echo -e '{content}' > /test/{fileName}"); + } + + public static async Task StartContainerAsync(string? folderNameToCopy = null, [CallerMemberName] string name = null!) + { + await GlobalImageBuilder.BuildImageAsync(); + var builder = new ContainerBuilder() + .WithName(GenerateContainerName(name)) + .WithImage("husky") + .WithWorkingDirectory("/test/") + .WithEntrypoint("/bin/bash", "-c") + .WithCleanUp(true) + .WithCommand("tail -f /dev/null"); + + if (!string.IsNullOrEmpty(folderNameToCopy)) + { + builder = builder.WithResourceMapping(GetTestFolderPath(folderNameToCopy), "/test/"); + } + + var container = builder.Build(); + await container.StartAsync(); + return container; + } + + public static async Task StartWithInstalledHusky([CallerMemberName] string name = null!) + { + await GlobalImageBuilder.BuildImageAsync(); + var c = await StartContainerAsync(nameof(TestProjectBase), name); + await c.BashAsync("git init"); + await c.BashAsync("dotnet new tool-manifest"); + await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky"); + await c.BashAsync("dotnet tool restore"); + await c.BashAsync("dotnet husky install"); + await c.BashAsync("git config --global user.email \"you@example.com\""); + await c.BashAsync("git config --global user.name \"Your Name\""); + await c.BashAsync("git add ."); + await c.BashAsync("git commit -m 'initial commit'"); + return c; + } + + private static string GenerateContainerName(string name) + { + return $"{name}-{Guid.NewGuid().ToString("N")[..4]}"; + } + + private static string GetTestFolderPath(string folderName) + { + var baseDirectory = CommonDirectoryPath.GetProjectDirectory().DirectoryPath; + return Path.Combine(baseDirectory, folderName); + } +} diff --git a/tests/HuskyIntegrationTests/Utilities/DockerLogger.cs b/tests/HuskyIntegrationTests/Utilities/DockerLogger.cs deleted file mode 100644 index fd4da4a..0000000 --- a/tests/HuskyIntegrationTests/Utilities/DockerLogger.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace HuskyIntegrationTests; - -public class DockerLogger(ITestOutputHelper output) : ILogger -{ - public IDisposable BeginScope(TState state) -{ - return null!; // You can implement if needed -} - -public bool IsEnabled(LogLevel logLevel) -{ - // Adjust the log level as needed - return true; -} - -public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) -{ - if (!IsEnabled(logLevel)) - { - return; - } - - var logMessage = formatter(state, exception); - - // Write to ITestOutputHelper - output.WriteLine($"[{logLevel}] {logMessage}"); -} -} diff --git a/tests/HuskyIntegrationTests/Utilities/Extensions.cs b/tests/HuskyIntegrationTests/Utilities/Extensions.cs deleted file mode 100644 index 39d5d69..0000000 --- a/tests/HuskyIntegrationTests/Utilities/Extensions.cs +++ /dev/null @@ -1,41 +0,0 @@ -using DotNet.Testcontainers.Containers; - -namespace HuskyIntegrationTests; - -public static class Extensions -{ - public const string SuccessfullyExecuted = "✔ Successfully executed"; - public const string Skipped = "💤 Skipped, no matched files"; - - public static async Task BashAsync(this IContainer container, params string[] command) - { - var result = await container.ExecAsync(["/bin/bash", "-c", ..command]); - if (result.ExitCode != 0) - throw new Exception(result.Stderr); - return result; - } - - public static async Task BashAsync(this IContainer container, ITestOutputHelper output, params string[] command) - { - var result = await container.ExecAsync(["/bin/bash", "-c", ..command]); - output.WriteLine($"{string.Join(" ", command)}:"); - - if (!string.IsNullOrEmpty(result.Stdout)) - output.WriteLine(result.Stdout); - - if (!string.IsNullOrEmpty(result.Stderr)) - output.WriteLine(result.Stderr); - - return result; - } - - public static Task UpdateTaskRunner(this IContainer container, string content) - { - return container.BashAsync($"echo -e '{content}' > /test/.husky/task-runner.json"); - } - - public static Task AddCsharpClass(this IContainer container, string content, string fileName = "Class2.cs") - { - return container.BashAsync($"echo -e '{content}' > /test/{fileName}"); - } -} diff --git a/tests/HuskyIntegrationTests/Utilities/GlobalImageBuilder.cs b/tests/HuskyIntegrationTests/Utilities/GlobalImageBuilder.cs new file mode 100644 index 0000000..d157bff --- /dev/null +++ b/tests/HuskyIntegrationTests/Utilities/GlobalImageBuilder.cs @@ -0,0 +1,63 @@ +using Docker.DotNet.Models; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Configurations; +using DotNet.Testcontainers.Containers; + +namespace HuskyIntegrationTests.Utilities; + +public static class GlobalImageBuilder +{ + /// + /// Set this value to false if you don't want the image to be removed after the test + /// This is useful if you want to debug the image, or fix tests issues + /// + private const bool RemoveHuskyImageAfterTest = true; + + + private static bool _imageBuilt; + private static readonly SemaphoreSlim SemaphoreSlim = new(1, 1); + + public static async ValueTask BuildImageAsync() + { + if (_imageBuilt) + { + return; + } + + await SemaphoreSlim.WaitAsync(); + try + { + if (!_imageBuilt && !await ImageExistsAsync("husky")) + { + var image = new ImageFromDockerfileBuilder() + .WithBuildArgument("RESOURCE_REAPER_SESSION_ID", ResourceReaper.DefaultSessionId.ToString("D")) + .WithDockerfileDirectory(CommonDirectoryPath.GetSolutionDirectory(), string.Empty) + .WithDockerfile("Dockerfile") + .WithName("husky") + .WithCleanUp(RemoveHuskyImageAfterTest) + .Build(); + + await image.CreateAsync(); + _imageBuilt = true; + } + } + finally + { + SemaphoreSlim.Release(); + } + } + + private static async Task ImageExistsAsync(string imageName) + { + var clientConfiguration = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(); + using var dockerClient = clientConfiguration.CreateClient(); + var images = await dockerClient.Images.ListImagesAsync(new ImagesListParameters + { + Filters = new Dictionary> + { + ["reference"] = new Dictionary { [imageName] = true } + } + }); + return _imageBuilt = images.Count > 0; + } +} diff --git a/tests/HuskyIntegrationTests/xunit.runner.json b/tests/HuskyIntegrationTests/xunit.runner.json new file mode 100644 index 0000000..b63d12d --- /dev/null +++ b/tests/HuskyIntegrationTests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "showLiveOutput": true +} From 05881170bb0ac28eb1db4ef89330a1ec6471e249 Mon Sep 17 00:00:00 2001 From: AliReZa Sabouri Date: Sun, 9 Jun 2024 22:32:47 +0200 Subject: [PATCH 3/7] test: enable remote debugging inside the container --- Dockerfile | 33 +++++++++---------- Husky.sln | 10 ++++-- src/Husky/Husky.csproj | 9 +++++ src/Husky/Program.cs | 20 +++++++++++ tests/HuskyIntegrationTests/Readme.md | 8 +++++ tests/HuskyIntegrationTests/Tests.cs | 14 ++++++-- .../Utilities/DockerHelper.cs | 8 ++++- 7 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 tests/HuskyIntegrationTests/Readme.md diff --git a/Dockerfile b/Dockerfile index 2cf2112..5f6a4e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,3 @@ -# This dockerfile is used in the integration tests - # Use the official .NET SDK image as a base FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env ARG RESOURCE_REAPER_SESSION_ID="00000000-0000-0000-0000-000000000000" @@ -8,22 +6,22 @@ LABEL "org.testcontainers.resource-reaper-session"=$RESOURCE_REAPER_SESSION_ID # Set the working directory WORKDIR /app -# Copy the .csproj file to the container -COPY src/Husky/Husky.csproj ./ - ENV HUSKY=0 -# Copy the remaining files to the container -COPY . ./ +# Copy the .csproj file to the container +COPY src/Husky/Husky.csproj ./src/Husky/ # Restore dependencies RUN dotnet restore /app/src/Husky -# Build the application -RUN dotnet build --no-restore -c Release -f net8.0 /app/src/Husky +# Copy the remaining files to the container +COPY . ./ -# Create a NuGet package -RUN dotnet pack --no-build --no-restore -c Release -o out /app/src/Husky/Husky.csproj -p:TargetFrameworks=net8.0 +# Build the application using the custom 'IntegrationTest' configuration +RUN dotnet build --no-restore -c IntegrationTest -f net8.0 /app/src/Husky/Husky.csproj -p:Version=99.1.1-test -p:TargetFrameworks=net8.0 + +# Create a NuGet package using the 'IntegrationTest' configuration +RUN dotnet pack --no-build --no-restore -c IntegrationTest -o out /app/src/Husky/Husky.csproj -p:Version=99.1.1-test -p:TargetFrameworks=net8.0 # Use the same .NET SDK image for the final stage FROM mcr.microsoft.com/dotnet/sdk:8.0 @@ -35,14 +33,15 @@ WORKDIR /app # Install Git RUN apt-get update && \ - apt-get install -y git + apt-get install -y git && \ + rm -rf /var/lib/apt/lists/* # Copy the NuGet package from the build-env to the runtime image COPY --from=build-env /app/out/*.nupkg /app/nupkg/ -# Install Husky tool and add the global tools path to the PATH -RUN dotnet tool install -g --no-cache --add-source /app/nupkg/ husky \ - && echo "export PATH=\$PATH:/root/.dotnet/tools" >> ~/.bashrc +# Install the specific version from the local source +RUN dotnet tool install -g husky --version 99.1.1-test --add-source /app/nupkg/ --no-cache + +RUN echo "export PATH=\$PATH:/root/.dotnet/tools" >> ~/.bashrc -# Set the entry point to a simple shell -ENTRYPOINT ["/bin/bash"] +CMD ["/root/.dotnet/tools/husky"] diff --git a/Husky.sln b/Husky.sln index 1ee66cd..34fda1b 100644 --- a/Husky.sln +++ b/Husky.sln @@ -13,20 +13,26 @@ Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + IntegrationTest|Any CPU = IntegrationTest|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {12AB4B33-47A6-49D5-872A-5BA6DD634E9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {12AB4B33-47A6-49D5-872A-5BA6DD634E9C}.Debug|Any CPU.Build.0 = Debug|Any CPU {12AB4B33-47A6-49D5-872A-5BA6DD634E9C}.Release|Any CPU.ActiveCfg = Release|Any CPU {12AB4B33-47A6-49D5-872A-5BA6DD634E9C}.Release|Any CPU.Build.0 = Release|Any CPU + {12AB4B33-47A6-49D5-872A-5BA6DD634E9C}.IntegrationTest|Any CPU.ActiveCfg = IntegrationTest|Any CPU + {12AB4B33-47A6-49D5-872A-5BA6DD634E9C}.IntegrationTest|Any CPU.Build.0 = IntegrationTest|Any CPU + {12AB4B33-47A6-49D5-872A-5BA6DD634E9C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.Release|Any CPU.Build.0 = Release|Any CPU + {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.IntegrationTest|Any CPU.ActiveCfg = IntegrationTest|Any CPU + {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.IntegrationTest|Any CPU.Build.0 = IntegrationTest|Any CPU {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.Debug|Any CPU.Build.0 = Debug|Any CPU {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.Release|Any CPU.Build.0 = Release|Any CPU + {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.IntegrationTest|Any CPU.ActiveCfg = IntegrationTest|Any CPU + {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.IntegrationTest|Any CPU.Build.0 = IntegrationTest|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {57EE798B-0FE0-42A4-BDB9-D168109D3E7D} = {302A5F25-CB44-4ED0-A65E-06C04648D211} diff --git a/src/Husky/Husky.csproj b/src/Husky/Husky.csproj index 86441d5..bcb6a29 100644 --- a/src/Husky/Husky.csproj +++ b/src/Husky/Husky.csproj @@ -26,6 +26,8 @@ snupkg true README.md + Debug;Release;IntegrationTest + AnyCPU @@ -60,4 +62,11 @@ + + + TEST + false + bin\IntegrationTest\ + obj\IntegrationTest\ + diff --git a/src/Husky/Program.cs b/src/Husky/Program.cs index 534d0cc..8db434f 100644 --- a/src/Husky/Program.cs +++ b/src/Husky/Program.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.IO.Abstractions; using System.Text.RegularExpressions; using CliFx; @@ -14,6 +15,10 @@ var exitCode = 0; +#if TEST +WaitForDebuggerIfNeeded(); +#endif + #if DEBUG "Starting development mode ... ".Log(ConsoleColor.DarkGray); while (true) @@ -73,3 +78,18 @@ ServiceProvider BuildServiceProvider() .AddTransient(); return services.BuildServiceProvider(); } + +#if TEST + static void WaitForDebuggerIfNeeded() + { + if (Environment.GetEnvironmentVariable("HUSKY_INTEGRATION_TEST") != "1") return; + Console.WriteLine("Waiting for debugger to attach..."); + + while (!Debugger.IsAttached) + { + Thread.Sleep(100); + } + + Console.WriteLine("Debugger attached."); + } +#endif diff --git a/tests/HuskyIntegrationTests/Readme.md b/tests/HuskyIntegrationTests/Readme.md new file mode 100644 index 0000000..e9f1dac --- /dev/null +++ b/tests/HuskyIntegrationTests/Readme.md @@ -0,0 +1,8 @@ +To debug the tests remotely: + +1. Run the test in debug mode. +2. The test will freeze when Husky processes run. +3. In Rider, go to "Attach to Remote Process." +4. Select Docker and locate the test container. +5. Install remote debugging tools and select the running .NET process. +6. It should hit the breakpoint in the code diff --git a/tests/HuskyIntegrationTests/Tests.cs b/tests/HuskyIntegrationTests/Tests.cs index 069aa3d..a633936 100644 --- a/tests/HuskyIntegrationTests/Tests.cs +++ b/tests/HuskyIntegrationTests/Tests.cs @@ -12,7 +12,7 @@ public async Task IntegrationTestCopyFolder() await c.BashAsync("git init"); await c.BashAsync("dotnet new tool-manifest"); - await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky"); + await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky --version 99.1.1-test"); await c.BashAsync("dotnet tool restore"); await c.BashAsync("dotnet husky install"); var result = await c.BashAsync(output, "dotnet husky run"); @@ -28,7 +28,7 @@ public async Task IntegrationTest() await c.BashAsync("dotnet new classlib"); await c.BashAsync("dotnet new tool-manifest"); - await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky"); + await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky --version 99.1.1-test"); await c.BashAsync("git init"); await c.BashAsync("dotnet husky install"); var result = await c.BashAsync(output, "dotnet husky run"); @@ -36,4 +36,14 @@ public async Task IntegrationTest() result.Stdout.Should().Contain(DockerHelper.SuccessfullyExecuted); result.ExitCode.Should().Be(0); } + + [Fact] + public async Task CheckVersion() + { + await using var c = await DockerHelper.StartWithInstalledHusky(); + var result = await c.BashAsync(output, "dotnet husky --version"); + + result.Stdout.Should().Contain("99.1.1"); + result.ExitCode.Should().Be(0); + } } diff --git a/tests/HuskyIntegrationTests/Utilities/DockerHelper.cs b/tests/HuskyIntegrationTests/Utilities/DockerHelper.cs index 2e5d467..4e92fac 100644 --- a/tests/HuskyIntegrationTests/Utilities/DockerHelper.cs +++ b/tests/HuskyIntegrationTests/Utilities/DockerHelper.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Runtime.CompilerServices; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; @@ -53,6 +54,11 @@ public static async Task StartContainerAsync(string? folderNameToCop .WithCleanUp(true) .WithCommand("tail -f /dev/null"); + if (Debugger.IsAttached) + { + builder = builder.WithEnvironment("HUSKY_INTEGRATION_TEST", "1"); + } + if (!string.IsNullOrEmpty(folderNameToCopy)) { builder = builder.WithResourceMapping(GetTestFolderPath(folderNameToCopy), "/test/"); @@ -69,7 +75,7 @@ public static async Task StartWithInstalledHusky([CallerMemberName] var c = await StartContainerAsync(nameof(TestProjectBase), name); await c.BashAsync("git init"); await c.BashAsync("dotnet new tool-manifest"); - await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky"); + await c.BashAsync("dotnet tool install --no-cache --add-source /app/nupkg/ husky --version 99.1.1-test"); await c.BashAsync("dotnet tool restore"); await c.BashAsync("dotnet husky install"); await c.BashAsync("git config --global user.email \"you@example.com\""); From 1c5809e51b0ce860345cd24a54fd49cb5a52b79c Mon Sep 17 00:00:00 2001 From: AliReZa Sabouri Date: Sun, 16 Jun 2024 18:01:25 +0200 Subject: [PATCH 4/7] feat: add FilteringRules --- docs/.vuepress/public/schema.json | 13 ++++++- src/Husky/TaskRunner/ArgumentParser.cs | 2 +- src/Husky/TaskRunner/ExecutableTaskFactory.cs | 37 ++++++++++++++++--- src/Husky/TaskRunner/FilteringRules.cs | 7 ++++ src/Husky/TaskRunner/HuskyTask.cs | 1 + 5 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 src/Husky/TaskRunner/FilteringRules.cs diff --git a/docs/.vuepress/public/schema.json b/docs/.vuepress/public/schema.json index a808065..b196391 100644 --- a/docs/.vuepress/public/schema.json +++ b/docs/.vuepress/public/schema.json @@ -79,6 +79,11 @@ }, "description": "Glob pattern to exclude files." }, + "filteringRule": { + "$ref": "#/definitions/filteringRules", + "description": "The filtering rule for this task. Can be 'variable' or 'staged'.", + "default": "variable" + }, "windows": { "$ref": "#/definitions/windowsOverrides", "description": "Overrides all settings for Windows." @@ -211,6 +216,12 @@ "enum": ["relative", "absolute"], "description": "Specifies the file path style.", "default": "relative" - } + }, + "filteringRules": { + "type": "string", + "enum": ["variable", "staged"], + "default": "variable", + "description": "The filtering rule for this task. Can be 'variable' or 'staged'." + } } } diff --git a/src/Husky/TaskRunner/ArgumentParser.cs b/src/Husky/TaskRunner/ArgumentParser.cs index 05e67a4..91efa2c 100644 --- a/src/Husky/TaskRunner/ArgumentParser.cs +++ b/src/Husky/TaskRunner/ArgumentParser.cs @@ -302,7 +302,7 @@ private static void AddCustomArguments(List args, string[]? option "⚠️ No arguments passed to the run command".Husky(ConsoleColor.Yellow); } - private static Matcher GetPatternMatcher(HuskyTask task) + public static Matcher GetPatternMatcher(HuskyTask task) { var matcher = new Matcher(); var hasMatcher = false; diff --git a/src/Husky/TaskRunner/ExecutableTaskFactory.cs b/src/Husky/TaskRunner/ExecutableTaskFactory.cs index b5eecbd..132d14d 100644 --- a/src/Husky/TaskRunner/ExecutableTaskFactory.cs +++ b/src/Husky/TaskRunner/ExecutableTaskFactory.cs @@ -3,6 +3,7 @@ using Husky.Stdout; using Husky.Utils; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileSystemGlobbing; namespace Husky.TaskRunner; @@ -38,11 +39,8 @@ public ExecutableTaskFactory(IServiceProvider provider, IGit git, IArgumentParse var cwd = await _git.GetTaskCwdAsync(huskyTask); var argsInfo = await _argumentParser.ParseAsync(huskyTask, options.Arguments?.ToArray()); - if (huskyTask.Args != null && huskyTask.Args.Length > argsInfo.Length) - { - "💤 Skipped, no matched files".Husky(ConsoleColor.Blue); - return null; - } + if (await CheckIfWeShouldSkipTheTask(huskyTask, argsInfo)) + return null; // skip the task // check for chunk var totalCommandLength = argsInfo.Sum(q => q.Argument.Length) + huskyTask.Command.Length; @@ -65,6 +63,35 @@ public ExecutableTaskFactory(IServiceProvider provider, IGit git, IArgumentParse ); } + private async Task CheckIfWeShouldSkipTheTask(HuskyTask huskyTask, ArgumentInfo[] argsInfo) + { + if (huskyTask is { FilteringRule: FilteringRules.Variable, Args: not null } && huskyTask.Args.Length > argsInfo.Length) + { + "💤 Skipped, no matched files".Husky(ConsoleColor.Blue); + return true; + } + + if (huskyTask.FilteringRule != FilteringRules.Staged) return false; + + var stagedFiles = (await _git.GetStagedFilesAsync()) + .Where(q => !string.IsNullOrWhiteSpace(q)) + .ToArray(); + if (stagedFiles.Length == 0) + { + "💤 Skipped, no staged files".Husky(ConsoleColor.Blue); + return true; + } + + var matcher = ArgumentParser.GetPatternMatcher(huskyTask); + + // get match staged files with glob + var matches = matcher.Match(stagedFiles); + if (matches.HasMatches) return false; + "💤 Skipped, no staged matched files".Husky(ConsoleColor.Blue); + return true; + + } + private IExecutableTask CreateChunkTask( HuskyTask huskyTask, int totalCommandLength, diff --git a/src/Husky/TaskRunner/FilteringRules.cs b/src/Husky/TaskRunner/FilteringRules.cs new file mode 100644 index 0000000..690c869 --- /dev/null +++ b/src/Husky/TaskRunner/FilteringRules.cs @@ -0,0 +1,7 @@ +namespace Husky.TaskRunner; + +public enum FilteringRules +{ + Variable, + Staged, +} diff --git a/src/Husky/TaskRunner/HuskyTask.cs b/src/Husky/TaskRunner/HuskyTask.cs index cc56abc..e8c5811 100644 --- a/src/Husky/TaskRunner/HuskyTask.cs +++ b/src/Husky/TaskRunner/HuskyTask.cs @@ -11,6 +11,7 @@ public class HuskyTask public string? Group { get; set; } public string? Branch { get; set; } public HuskyTask? Windows { get; set; } + public FilteringRules FilteringRule { get; set; } = FilteringRules.Variable; public string[]? Include { get; set; } public string[]? Exclude { get; set; } } From 2d3ffc6e42827e26d987d5bffa2a95be8160714a Mon Sep 17 00:00:00 2001 From: AliReZa Sabouri Date: Sun, 16 Jun 2024 18:01:57 +0200 Subject: [PATCH 5/7] test: test skip when no match files found --- tests/HuskyIntegrationTests/Issue106Tests.cs | 1 + tests/HuskyIntegrationTests/Utilities/DockerHelper.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/HuskyIntegrationTests/Issue106Tests.cs b/tests/HuskyIntegrationTests/Issue106Tests.cs index a3b6ee7..4efc186 100644 --- a/tests/HuskyIntegrationTests/Issue106Tests.cs +++ b/tests/HuskyIntegrationTests/Issue106Tests.cs @@ -34,6 +34,7 @@ private async Task ArrangeContainer([CallerMemberName] string name = "name": "EchoWithInclude", "group": "pre-commit", "command": "bash", + "filteringRule": "staged", "args": [ "-c", "echo Husky.Net is awesome!" diff --git a/tests/HuskyIntegrationTests/Utilities/DockerHelper.cs b/tests/HuskyIntegrationTests/Utilities/DockerHelper.cs index 4e92fac..d98f696 100644 --- a/tests/HuskyIntegrationTests/Utilities/DockerHelper.cs +++ b/tests/HuskyIntegrationTests/Utilities/DockerHelper.cs @@ -9,7 +9,7 @@ namespace HuskyIntegrationTests; public static class DockerHelper { public const string SuccessfullyExecuted = "✔ Successfully executed"; - public const string Skipped = "💤 Skipped, no matched files"; + public const string Skipped = "💤 Skipped,"; public static async Task BashAsync(this IContainer container, params string[] command) { From 34e2e33c3631aef9793acf02fc77cbbb0240acf9 Mon Sep 17 00:00:00 2001 From: AliReZa Sabouri Date: Sun, 16 Jun 2024 18:09:28 +0200 Subject: [PATCH 6/7] fix: update sln file --- Husky.sln | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Husky.sln b/Husky.sln index 34fda1b..37fbf60 100644 --- a/Husky.sln +++ b/Husky.sln @@ -22,17 +22,14 @@ Global {12AB4B33-47A6-49D5-872A-5BA6DD634E9C}.Release|Any CPU.Build.0 = Release|Any CPU {12AB4B33-47A6-49D5-872A-5BA6DD634E9C}.IntegrationTest|Any CPU.ActiveCfg = IntegrationTest|Any CPU {12AB4B33-47A6-49D5-872A-5BA6DD634E9C}.IntegrationTest|Any CPU.Build.0 = IntegrationTest|Any CPU - {12AB4B33-47A6-49D5-872A-5BA6DD634E9C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.Release|Any CPU.Build.0 = Release|Any CPU - {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.IntegrationTest|Any CPU.ActiveCfg = IntegrationTest|Any CPU - {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.IntegrationTest|Any CPU.Build.0 = IntegrationTest|Any CPU + {57EE798B-0FE0-42A4-BDB9-D168109D3E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.Release|Any CPU.Build.0 = Release|Any CPU - {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.IntegrationTest|Any CPU.ActiveCfg = IntegrationTest|Any CPU - {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.IntegrationTest|Any CPU.Build.0 = IntegrationTest|Any CPU + {FAF049CD-6E45-4A66-A88F-C2EB007DFCC3}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {57EE798B-0FE0-42A4-BDB9-D168109D3E7D} = {302A5F25-CB44-4ED0-A65E-06C04648D211} From 8cf0e5cf5c234c8acf0bb8960f341e6573ac0b1d Mon Sep 17 00:00:00 2001 From: AliReZa Sabouri Date: Sun, 16 Jun 2024 19:13:36 +0200 Subject: [PATCH 7/7] test: add integration tests --- tests/HuskyIntegrationTests/Issue106Tests.cs | 259 +++++++++++++++++-- 1 file changed, 239 insertions(+), 20 deletions(-) diff --git a/tests/HuskyIntegrationTests/Issue106Tests.cs b/tests/HuskyIntegrationTests/Issue106Tests.cs index 4efc186..5b12890 100644 --- a/tests/HuskyIntegrationTests/Issue106Tests.cs +++ b/tests/HuskyIntegrationTests/Issue106Tests.cs @@ -6,10 +6,59 @@ namespace HuskyIntegrationTests; public class Issue106Tests (ITestOutputHelper output) { [Fact] - public async Task EchoWithIncludeTask_WhenNoMatchFilesFound_ShouldSkip() + public async Task FilteringRuleNotDefined_WithInclude_ShouldNotSkip() { // arrange - await using var c = await ArrangeContainer(); + const string taskRunner = + """ + { + "tasks": [ + { + "name": "Echo", + "command": "echo", + "args": [ + "Husky.Net is awesome!" + ], + "include": [ + "client/**/*" + ] + } + ] + } + """; + await using var c = await ArrangeContainer(taskRunner); + await c.BashAsync("git add ."); + + // act + var result = await c.BashAsync(output, "git commit -m 'add task-runner.json'"); + + // assert + result.ExitCode.Should().Be(0); + result.Stderr.Should().NotContain(DockerHelper.Skipped); + } + + [Fact] + public async Task FilteringRuleNotDefined_WithStagedVariable_WithExcludeCommitedFile_ShouldSkip() + { + // arrange + const string taskRunner = + """ + { + "tasks": [ + { + "name": "Echo", + "command": "echo", + "args": [ + "${staged}" + ], + "exclude": [ + "**/task-runner.json" + ] + } + ] + } + """; + await using var c = await ArrangeContainer(taskRunner); await c.BashAsync("git add ."); // act @@ -20,32 +69,89 @@ public async Task EchoWithIncludeTask_WhenNoMatchFilesFound_ShouldSkip() result.Stderr.Should().Contain(DockerHelper.Skipped); } - private async Task ArrangeContainer([CallerMemberName] string name = null!) + [Fact] + public async Task FilteringRuleVariable_WithStagedVariable_WithExcludeCommitedFile_ShouldSkip() { - var c = await DockerHelper.StartWithInstalledHusky(name); - await c.BashAsync("dotnet tool restore"); + // arrange + const string taskRunner = + """ + { + "tasks": [ + { + "name": "Echo", + "command": "echo", + "filteringRule": "variable", + "args": [ + "${staged}" + ], + "exclude": [ + "**/task-runner.json" + ] + } + ] + } + """; + await using var c = await ArrangeContainer(taskRunner); await c.BashAsync("git add ."); - const string tasks = + // act + var result = await c.BashAsync(output, "git commit -m 'add task-runner.json'"); + + // assert + result.ExitCode.Should().Be(0); + result.Stderr.Should().Contain(DockerHelper.Skipped); + } + + [Fact] + public async Task FilteringRuleVariable_WithStagedVariable_WithIncludeCommitedFile_ShouldNotSkip() + { + // arrange + const string taskRunner = """ { "tasks": [ { - "name": "EchoWithInclude", - "group": "pre-commit", - "command": "bash", + "name": "Echo", + "command": "echo", + "pathMode": "absolute", + "filteringRule": "variable", + "args": [ + "${staged}" + ], + "include": [ + "**/task-runner.json" + ] + } + ] + } + """; + await using var c = await ArrangeContainer(taskRunner); + await c.BashAsync("git add ."); + + // act + var result = await c.BashAsync(output, "git commit -m 'add task-runner.json'"); + + // assert + result.ExitCode.Should().Be(0); + result.Stderr.Should().NotContain(DockerHelper.Skipped); + result.Stderr.Should().Contain(".husky/task-runner.json"); + } + + [Fact] + public async Task FilteringRuleStaged_WithoutAnyMatchedFile_ShouldSkip() + { + // arrange + const string taskRunner = + """ + { + "tasks": [ + { + "name": "Echo", + "command": "echo", "filteringRule": "staged", "args": [ - "-c", - "echo Husky.Net is awesome!" + "Husky.Net is awesome!" ], - "windows": { - "command": "cmd", - "args": [ - "/c", - "echo Husky.Net is awesome!" - ] - }, "include": [ "client/**/*" ] @@ -53,8 +159,121 @@ private async Task ArrangeContainer([CallerMemberName] string name = ] } """; - await c.UpdateTaskRunner(tasks); - await c.BashAsync("dotnet husky add pre-commit -c 'dotnet husky run -g pre-commit'"); + await using var c = await ArrangeContainer(taskRunner); + await c.BashAsync("git add ."); + + // act + var result = await c.BashAsync(output, "git commit -m 'add task-runner.json'"); + + // assert + result.ExitCode.Should().Be(0); + result.Stderr.Should().Contain(DockerHelper.Skipped); + } + + [Fact] + public async Task FilteringRuleStaged_WithoutIncludeAndExclude_ShouldNotSkip() + { + // arrange + const string taskRunner = + """ + { + "tasks": [ + { + "name": "Echo", + "command": "echo", + "filteringRule": "staged", + "args": [ + "Husky.Net is awesome!" + ] + } + ] + } + """; + await using var c = await ArrangeContainer(taskRunner); + await c.BashAsync("git add ."); + + // act + var result = await c.BashAsync(output, "git commit -m 'add task-runner.json'"); + + // assert + result.ExitCode.Should().Be(0); + result.Stderr.Should().NotContain(DockerHelper.Skipped); + } + + [Fact] + public async Task FilteringRuleStaged_WithExcludeCommitedFile_ShouldSkip() + { + // arrange + const string taskRunner = + """ + { + "tasks": [ + { + "name": "Echo", + "command": "echo", + "filteringRule": "staged", + "args": [ + "Husky.Net is awesome!" + ], + "exclude": [ + "**/task-runner.json" + ] + } + ] + } + """; + await using var c = await ArrangeContainer(taskRunner); + await c.BashAsync("git add ."); + + // act + var result = await c.BashAsync(output, "git commit -m 'add task-runner.json'"); + + // assert + result.ExitCode.Should().Be(0); + result.Stderr.Should().Contain(DockerHelper.Skipped); + } + + [Fact] + public async Task FilteringRuleStaged_WithIncludeCommitedFile_ShouldNotSkip() + { + // arrange + const string taskRunner = + """ + { + "tasks": [ + { + "name": "Echo", + "command": "echo", + "filteringRule": "staged", + "args": [ + "Husky.Net is awesome!" + ], + "include": [ + "**/task-runner.json" + ] + } + ] + } + """; + await using var c = await ArrangeContainer(taskRunner); + await c.BashAsync("git add ."); + + // act + var result = await c.BashAsync(output, "git commit -m 'add task-runner.json'"); + + // assert + result.ExitCode.Should().Be(0); + result.Stderr.Should().NotContain(DockerHelper.Skipped); + } + + + private async Task ArrangeContainer(string taskRunner, [CallerMemberName] string name = null!) + { + var c = await DockerHelper.StartWithInstalledHusky(name); + await c.BashAsync("dotnet tool restore"); + await c.BashAsync("git add ."); + await c.UpdateTaskRunner(taskRunner); + await c.BashAsync("dotnet husky add pre-commit -c 'dotnet husky run'"); return c; } }