Skip to content

Commit

Permalink
Fix input path directory handling #1842 (#2684)
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite authored Dec 21, 2024
1 parent a32ad2f commit 73722c0
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 140 deletions.
2 changes: 2 additions & 0 deletions docs/CHANGELOG-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ What's changed since pre-release v3.0.0-B0351:
- Bug fixes:
- Fixed string formatting of semantic version and constraints by @BernieWhite.
[#1828](https://github.com/microsoft/PSRule/issues/1828)
- Fixed directory handling of input paths without trailing slash by @BernieWhite.
[#1842](https://github.com/microsoft/PSRule/issues/1842)

## v3.0.0-B0351 (pre-release)

Expand Down
8 changes: 5 additions & 3 deletions src/PSRule/Pipeline/InputPathBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

namespace PSRule.Pipeline;

internal sealed class InputPathBuilder : PathBuilder
/// <summary>
/// A builder for input paths.
/// </summary>
internal sealed class InputPathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required)
: PathBuilder(logger, basePath, searchPattern, filter, required)
{
public InputPathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required)
: base(logger, basePath, searchPattern, filter, required) { }
}
66 changes: 27 additions & 39 deletions src/PSRule/Pipeline/PathBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,7 @@

namespace PSRule.Pipeline;

//public interface IPathBuilder
//{
// void Add(string path);

// void Add(FileInfo[] fileInfo);

// void Add(PathInfo[] pathInfo);

// InputFileInfo[] Build();
//}

internal abstract class PathBuilder
internal abstract class PathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required)
{
// Path separators
private const char Slash = '/';
Expand All @@ -28,27 +17,16 @@ internal abstract class PathBuilder
private const string CurrentPath = ".";
private const string RecursiveSearchOperator = "**";

private static readonly char[] PathLiteralStopCharacters = new char[] { '*', '[', '?' };
private static readonly char[] PathSeparatorCharacters = new char[] { '\\', '/' };
private static readonly char[] PathLiteralStopCharacters = ['*', '[', '?'];
private static readonly char[] PathSeparatorCharacters = ['\\', '/'];

private readonly IPipelineWriter _Logger;
private readonly List<InputFileInfo> _Files;
private readonly HashSet<string> _Paths;
private readonly string _BasePath;
private readonly string _DefaultSearchPattern;
private readonly PathFilter _GlobalFilter;
private readonly PathFilter _Required;

protected PathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required)
{
_Logger = logger;
_Files = [];
_Paths = [];
_BasePath = NormalizePath(Environment.GetRootedBasePath(basePath));
_DefaultSearchPattern = searchPattern;
_GlobalFilter = filter;
_Required = required;
}
private readonly IPipelineWriter _Logger = logger;
private readonly List<InputFileInfo> _Files = [];
private readonly HashSet<string> _Paths = [];
private readonly string _BasePath = NormalizePath(Environment.GetRootedBasePath(basePath));
private readonly string _DefaultSearchPattern = searchPattern;
private readonly PathFilter _GlobalFilter = filter;
private readonly PathFilter _Required = required;

/// <summary>
/// The number of files found.
Expand Down Expand Up @@ -89,7 +67,7 @@ public InputFileInfo[] Build()
{
try
{
return _Files.ToArray();
return [.. _Files];
}
finally
{
Expand All @@ -105,9 +83,14 @@ private void FindFiles(string path)

var pathLiteral = GetSearchParameters(path, out var searchPattern, out var searchOption, out var filter);
var files = Directory.EnumerateFiles(pathLiteral, searchPattern, searchOption);

foreach (var file in files)
{
if (ShouldInclude(file, filter))
{
AddFile(file);
}
}
}

private bool TryUrl(string path)
Expand All @@ -128,9 +111,7 @@ private bool TryPath(string path, out string normalPath)
var rootedPath = GetRootedPath(path);
if (Directory.Exists(rootedPath) || path == CurrentPath)
{
if (IsBasePath(rootedPath))
normalPath = CurrentPath;

normalPath = IsBasePath(rootedPath) ? CurrentPath : NormalizeDirectoryPath(path);
return false;
}
if (!File.Exists(rootedPath))
Expand All @@ -144,8 +125,7 @@ private bool TryPath(string path, out string normalPath)

private bool IsBasePath(string path)
{
path = IsSeparator(path[path.Length - 1]) ? path : string.Concat(path, Path.DirectorySeparatorChar);
return NormalizePath(path) == _BasePath;
return NormalizeDirectoryPath(path) == _BasePath;
}

private void ErrorNotFound(string path)
Expand Down Expand Up @@ -251,12 +231,20 @@ private static bool IsSeparator(char c)
[DebuggerStepThrough]
private static bool UseSimpleSearch(string s)
{
return s.IndexOf(RecursiveSearchOperator, System.StringComparison.OrdinalIgnoreCase) == -1;
return s.IndexOf(RecursiveSearchOperator, StringComparison.OrdinalIgnoreCase) == -1;
}

[DebuggerStepThrough]
private static string NormalizePath(string path)
{
return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
}

[DebuggerStepThrough]
private static string NormalizeDirectoryPath(string path)
{
return NormalizePath(
IsSeparator(path[path.Length - 1]) ? path : string.Concat(path, Path.DirectorySeparatorChar)
);
}
}
98 changes: 0 additions & 98 deletions tests/PSRule.Tests/InputPathBuilderTests.cs

This file was deleted.

100 changes: 100 additions & 0 deletions tests/PSRule.Tests/Pipeline/InputPathBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.IO;
using System.Linq;

namespace PSRule.Pipeline;

/// <summary>
/// Tests for <see cref="InputPathBuilder"/>.
/// </summary>
public sealed class InputPathBuilderTests
{
[Theory]
[InlineData("./.github/*.yml", 1)]
[InlineData("./.github/**/*.yaml", 9)]
[InlineData("./.github/", 12)]
[InlineData(".github/", 12)]
[InlineData(".github", 12)]
[InlineData("./*.json", 8)]
public void Build_WithValidPathAdded_ShouldReturnFiles(string path, int expected)
{
var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, null);
builder.Add(path);
var actual = builder.Build();

Assert.Equal(expected, actual.Length);
}

[Theory]
[InlineData(".")]
[InlineData("./")]
[InlineData("./src")]
[InlineData("./src/")]
[InlineData("src/")]
public void Build_WithValidPathAdded_ShouldReturnManyFiles(string path)
{
var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, null);
builder.Add(path);
var actual = builder.Build();

Assert.True(actual.Length > 100);
}

[Fact]
public void Build_WithWorkingPathAdded_ShouldReturnFiles()
{
var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, null);
builder.Add(GetWorkingPath());
var actual = builder.Build();

Assert.True(actual.Length > 100);
}

/// <summary>
/// Test that an invalid path is handled correctly.
/// Should not return any files, and should log an error.
/// </summary>
[Fact]
public void Build_WithInvalidPathAdded_ShouldReturnEmpty()
{
var writer = new TestWriter(new Configuration.PSRuleOption());
var builder = new InputPathBuilder(writer, GetWorkingPath(), "*", null, null);
builder.Add("ZZ://not/path");
var actual = builder.Build();

Assert.Empty(actual);
Assert.True(writer.Errors.Count(r => r.FullyQualifiedErrorId == "PSRule.ReadInputFailed") == 1);
}

[Fact]
public void GetPathRequired()
{
var required = PathFilter.Create(GetWorkingPath(), ["README.md"]);
var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, required);
builder.Add(".");
var actual = builder.Build();
Assert.True(actual.Length == 1);

builder.Add(GetWorkingPath());
actual = builder.Build();
Assert.True(actual.Length == 1);

required = PathFilter.Create(GetWorkingPath(), ["**"]);
builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, required);
builder.Add(".");
actual = builder.Build();
Assert.True(actual.Length > 100);
}

#region Helper methods

private static string GetWorkingPath()
{
return Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../../.."));
}

#endregion Helper methods
}

0 comments on commit 73722c0

Please sign in to comment.