Skip to content

Commit

Permalink
chore: Breaking down FilePattern (#2681)
Browse files Browse the repository at this point in the history
* Simplifying DiffIgnoreChangesInput

* Update FilePattern.cs

* Up

* up

* Rename the class

* up

---------

Co-authored-by: Rouke Broersma <[email protected]>
  • Loading branch information
psfinaki and rouke-broersma authored Oct 13, 2023
1 parent 0374632 commit b4f22e5
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public void ScanDiff_Throws_Stryker_Input_Exception_When_Commit_null()
public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles()
{
// Arrange
var diffIgnoreFiles = new[] { new FilePattern(Glob.Parse("/c/Users/JohnDoe/Project/Tests/Test.cs"), false, null) };
var diffIgnoreFiles = new[] { new ExclusionPattern("/c/Users/JohnDoe/Project/Tests/Test.cs") };

var basePath = FilePathUtils.NormalizePathSeparators("/c/Users/JohnDoe/Project/Tests");
var options = new StrykerOptions()
Expand Down Expand Up @@ -289,7 +289,7 @@ public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles()
public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles_Single_Asterisk()
{
// Arrange
var diffIgnoreFiles = new[] { new FilePattern(Glob.Parse("/c/Users/JohnDoe/Project/*/Test.cs"), false, null) };
var diffIgnoreFiles = new[] { new ExclusionPattern("/c/Users/JohnDoe/Project/*/Test.cs") };

var basePath = FilePathUtils.NormalizePathSeparators("/c/Users/JohnDoe/Project/Tests");
var options = new StrykerOptions()
Expand Down Expand Up @@ -365,7 +365,7 @@ public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles_Singl
public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles_Multi_Asterisk()
{
// Arrange
var diffIgnoreFiles = new[] { new FilePattern(Glob.Parse("**/Test.cs"), false, null) };
var diffIgnoreFiles = new[] { new ExclusionPattern("**/Test.cs") };

var basePath = FilePathUtils.NormalizePathSeparators("/c/Users/JohnDoe/Project/Tests");
var options = new StrykerOptions()
Expand Down Expand Up @@ -441,7 +441,7 @@ public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles_Multi
public void ScanDiffReturnsListOfFiles_ExcludingFilesInDiffIgnoreFiles_Multi_Asterisk()
{
// Arrange
var diffIgnoreFiles = new[] { new FilePattern(Glob.Parse("**/file.cs"), false, null) };
var diffIgnoreFiles = new[] { new ExclusionPattern("**/file.cs") };

var basePath = FilePathUtils.NormalizePathSeparators("/c/Users/JohnDoe/Project/Tests");
var options = new StrykerOptions()
Expand Down
36 changes: 36 additions & 0 deletions src/Stryker.Core/Stryker.Core.UnitTest/ExclusionPatternTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using Shouldly;
using Xunit;

namespace Stryker.Core.UnitTest
{
public class ExclusionPatternTests : TestBase
{
[Fact]
public void ExclusionPattern_Null()
{
_ = Assert.Throws<ArgumentNullException>(() => new ExclusionPattern(null));
}

[Fact]
public void ExclusionPattern_Globs()
{
var s1 = new ExclusionPattern(@"Person.cs");
var s2 = new ExclusionPattern(@"!Person.cs");

s1.IsExcluded.ShouldBeFalse();
s2.IsExcluded.ShouldBeTrue();
s1.Glob.ToString().ShouldBe(s2.Glob.ToString());
}

[Fact]
public void ExclusionPattern_MutantSpans()
{
var s1 = new ExclusionPattern(@"src/Person.cs{10..100}");
var s2 = new ExclusionPattern(@"src/Person.cs");

s1.MutantSpans.ShouldBe(new [] { (10, 100)});
s2.MutantSpans.ShouldBeEmpty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Shouldly;
using Xunit;

namespace Stryker.Core.UnitTest.Options
namespace Stryker.Core.UnitTest
{
public class FilePatternTests : TestBase
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public void ShouldCopyValues()
DashboardUrl = "url",
DevMode = true,
Since = true,
DiffIgnoreChanges = new[] { new FilePattern(Glob.Parse("**"), true, null) },
DiffIgnoreChanges = new[] { new ExclusionPattern("**") },
ExcludedMutations = new[] { Mutator.Bitwise },
FallbackVersion = "main",
IgnoredMethods = new[] { new Regex("") },
Expand Down
49 changes: 49 additions & 0 deletions src/Stryker.Core/Stryker.Core/ExclusionPattern.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using DotNet.Globbing;

namespace Stryker.Core
{
public readonly struct ExclusionPattern
{
private static readonly Regex _mutantSpanGroupRegex = new("(\\{(\\d+)\\.\\.(\\d+)\\})+$");
private static readonly Regex _mutantSpanRegex = new Regex("\\{(\\d+)\\.\\.(\\d+)\\}");

public ExclusionPattern(string s)
{
if (s is null)
{
throw new ArgumentNullException(nameof(s));
}

IsExcluded = s.StartsWith('!');

var pattern = IsExcluded ? s[1..] : s;
var mutantSpansRegex = _mutantSpanGroupRegex.Match(pattern);
if (mutantSpansRegex.Success)
{
var filePathPart = pattern[..^mutantSpansRegex.Length];
var normalized = FilePathUtils.NormalizePathSeparators(filePathPart);
Glob = Glob.Parse(normalized);

MutantSpans = _mutantSpanRegex
.Matches(mutantSpansRegex.Value)
.Select(x => (int.Parse(x.Groups[1].Value), int.Parse(x.Groups[2].Value)));
}
else
{
var normalized = FilePathUtils.NormalizePathSeparators(pattern);
Glob = Glob.Parse(normalized);
MutantSpans = Enumerable.Empty<(int, int)>();
}
}

public bool IsExcluded { get; }

public Glob Glob { get; }

public IEnumerable<(int Start, int End)> MutantSpans { get; }
}
}
37 changes: 6 additions & 31 deletions src/Stryker.Core/Stryker.Core/FilePattern.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Stryker.Core
{
Expand All @@ -13,8 +12,6 @@ namespace Stryker.Core
/// </summary>
public sealed class FilePattern : IEquatable<FilePattern>
{
private static readonly Regex _textSpanGroupRegex = new Regex("(\\{(\\d+)\\.\\.(\\d+)\\})+$");
private static readonly Regex _textSpanRegex = new Regex("\\{(\\d+)\\.\\.(\\d+)\\}");
private static readonly TextSpan _textSpanMaxValue = new TextSpan(0, int.MaxValue);

public FilePattern(Glob glob, bool isExclude, IReadOnlyCollection<TextSpan> textSpans)
Expand Down Expand Up @@ -45,47 +42,25 @@ public FilePattern(Glob glob, bool isExclude, IReadOnlyCollection<TextSpan> text
/// </summary>
/// <param name="pattern">The pattern to parse.</param>
/// <returns>The <see cref="FilePattern"/></returns>
public static FilePattern Parse(string pattern) => Parse(pattern, spansEnabled: true);

/// <summary>
/// Parses a given file pattern string.
/// Format: (!)&lt;glob&gt;({&lt;spanStart&gt;..&lt;spanEnd&gt;})*
/// </summary>
/// <param name="pattern">The pattern to parse.</param>
/// <param name="spansEnabled">Enable or disable span parsing.</param>
/// <returns>The <see cref="FilePattern"/></returns>
public static FilePattern Parse(string pattern, bool spansEnabled)
public static FilePattern Parse(string pattern)
{
var exclude = false;
var s = new ExclusionPattern(pattern);
IReadOnlyCollection<TextSpan> textSpans;

if (pattern.StartsWith('!'))
{
exclude = true;
pattern = pattern[1..];
}

var textSpanGroupMatch = _textSpanGroupRegex.Match(pattern);
if (!spansEnabled || !textSpanGroupMatch.Success)
if (!s.MutantSpans.Any())
{
// If there are no spans specified, we add one that will cover the whole file.
textSpans = new[] { _textSpanMaxValue };
}
else
{
// If we have one ore more spans we parse them.
var textSpansMatches = _textSpanRegex.Matches(textSpanGroupMatch.Value);
textSpans = textSpansMatches
.Select(x => TextSpan.FromBounds(int.Parse(x.Groups[1].Value), int.Parse(x.Groups[2].Value)))
textSpans = s.MutantSpans
.Select(x => TextSpan.FromBounds(x.Start, x.End))
.Reduce()
.ToList();

pattern = pattern.Substring(0, pattern.Length - textSpanGroupMatch.Length);
}

var glob = Glob.Parse(FilePathUtils.NormalizePathSeparators(pattern));

return new FilePattern(glob, exclude, textSpans);
return new FilePattern(s.Glob, s.IsExcluded, textSpans);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ Any non-excluded files will trigger all mutants to be tested because we cannot d
Use glob syntax for wildcards: https://en.wikipedia.org/wiki/Glob_(programming)
Example: ['**/*Assets.json','**/favicon.ico']";

public IEnumerable<FilePattern> Validate()
public IEnumerable<ExclusionPattern> Validate()
{
if (SuppliedInput is { })
{
var diffIgnoreFilePatterns = new List<FilePattern>();
var diffIgnoreStrings = new List<ExclusionPattern>();
foreach (var pattern in SuppliedInput)
{
diffIgnoreFilePatterns.Add(FilePattern.Parse(FilePathUtils.NormalizePathSeparators(pattern), spansEnabled: false));
diffIgnoreStrings.Add(new ExclusionPattern(FilePathUtils.NormalizePathSeparators(pattern)));
}

return diffIgnoreFilePatterns;
return diffIgnoreStrings;
}
return Enumerable.Empty<FilePattern>();
return Enumerable.Empty<ExclusionPattern>();
}
}
}
2 changes: 1 addition & 1 deletion src/Stryker.Core/Stryker.Core/Options/StrykerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public class StrykerOptions
/// Context: When using the since feature, all tests are run again if files in the test project change (as these could impact the test results)
/// When the file is present in this option the tests should not run again as the file does not impact test results.
/// </summary>
public IEnumerable<FilePattern> DiffIgnoreChanges { get; init; } = Enumerable.Empty<FilePattern>();
public IEnumerable<ExclusionPattern> DiffIgnoreChanges { get; init; } = Enumerable.Empty<ExclusionPattern>();

/// <summary>
/// When no previous report can be found for the since feature, this commitish is used to se a baseline.
Expand Down

0 comments on commit b4f22e5

Please sign in to comment.