Skip to content

Commit 9e6ee57

Browse files
committed
Decoupling file excludability from mutant spans
1 parent 0884dc1 commit 9e6ee57

24 files changed

+388
-184
lines changed

src/Stryker.Core/Stryker.Core.UnitTest/DiffProviders/GitDiffProviderTests.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Stryker.Core.DiffProviders;
1010
using Stryker.Core.Exceptions;
1111
using Stryker.Core.Options;
12+
using Stryker.Core.ProjectComponents;
1213
using Xunit;
1314

1415
namespace Stryker.Core.UnitTest.DiffProviders
@@ -213,7 +214,7 @@ public void ScanDiff_Throws_Stryker_Input_Exception_When_Commit_null()
213214
public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles()
214215
{
215216
// Arrange
216-
var diffIgnoreFiles = new[] { new FilePattern(Glob.Parse("/c/Users/JohnDoe/Project/Tests/Test.cs"), false, null) };
217+
var diffIgnoreFiles = new[] { new ExcludableString("/c/Users/JohnDoe/Project/Tests/Test.cs") };
217218

218219
var basePath = FilePathUtils.NormalizePathSeparators("/c/Users/JohnDoe/Project/Tests");
219220
var options = new StrykerOptions()
@@ -289,7 +290,7 @@ public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles()
289290
public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles_Single_Asterisk()
290291
{
291292
// Arrange
292-
var diffIgnoreFiles = new[] { new FilePattern(Glob.Parse("/c/Users/JohnDoe/Project/*/Test.cs"), false, null) };
293+
var diffIgnoreFiles = new[] { new ExcludableString("/c/Users/JohnDoe/Project/*/Test.cs") };
293294

294295
var basePath = FilePathUtils.NormalizePathSeparators("/c/Users/JohnDoe/Project/Tests");
295296
var options = new StrykerOptions()
@@ -365,7 +366,7 @@ public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles_Singl
365366
public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles_Multi_Asterisk()
366367
{
367368
// Arrange
368-
var diffIgnoreFiles = new[] { new FilePattern(Glob.Parse("**/Test.cs"), false, null) };
369+
var diffIgnoreFiles = new[] { new ExcludableString("**/Test.cs") };
369370

370371
var basePath = FilePathUtils.NormalizePathSeparators("/c/Users/JohnDoe/Project/Tests");
371372
var options = new StrykerOptions()
@@ -441,7 +442,7 @@ public void ScanDiffReturnsListOfFiles_ExcludingTestFilesInDiffIgnoreFiles_Multi
441442
public void ScanDiffReturnsListOfFiles_ExcludingFilesInDiffIgnoreFiles_Multi_Asterisk()
442443
{
443444
// Arrange
444-
var diffIgnoreFiles = new[] { new FilePattern(Glob.Parse("**/file.cs"), false, null) };
445+
var diffIgnoreFiles = new[] { new ExcludableString("**/file.cs") };
445446

446447
var basePath = FilePathUtils.NormalizePathSeparators("/c/Users/JohnDoe/Project/Tests");
447448
var options = new StrykerOptions()

src/Stryker.Core/Stryker.Core.UnitTest/MutantFilters/FilePatternMutantFilterTests.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using Shouldly;
66
using Stryker.Core.MutantFilters;
77
using Stryker.Core.Mutants;
8-
using Stryker.Core.Options;
98
using Stryker.Core.ProjectComponents;
109
using Xunit;
1110

@@ -16,7 +15,7 @@ public class FilePatternMutantFilterTests : TestBase
1615
[Fact]
1716
public static void ShouldHaveName()
1817
{
19-
var target = new FilePatternMutantFilter(new StrykerOptions()) as IMutantFilter;
18+
var target = new FilePatternMutantFilter();
2019
target.DisplayName.ShouldBe("mutate filter");
2120
}
2221

@@ -42,8 +41,8 @@ public void FilterMutants_should_filter_included_and_excluded_files(
4241
bool shouldKeepFile)
4342
{
4443
// Arrange
45-
var options = new StrykerOptions() { Mutate = patterns.Select(FilePattern.Parse) };
46-
var file = new CsharpFileLeaf { RelativePath = filePath, FullPath = Path.Combine("C:/test/", filePath) };
44+
var strings = patterns.Select(ExcludableString.Parse);
45+
var file = new CsharpFileLeaf(strings) { RelativePath = filePath, FullPath = Path.Combine("C:/test/", filePath) };
4746

4847
// Create token with the correct text span
4948
var syntaxToken = SyntaxFactory.Identifier(
@@ -54,7 +53,7 @@ public void FilterMutants_should_filter_included_and_excluded_files(
5453
var mutant = new Mutant
5554
{ Mutation = new Mutation { OriginalNode = SyntaxFactory.IdentifierName(syntaxToken) } };
5655

57-
var sut = new FilePatternMutantFilter(options);
56+
var sut = new FilePatternMutantFilter();
5857

5958
// Act
6059
var result = sut.FilterMutants(new[] { mutant }, file, null);

src/Stryker.Core/Stryker.Core.UnitTest/Options/FilePatternTests.cs

+11-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using DotNet.Globbing;
44
using Microsoft.CodeAnalysis.Text;
55
using Shouldly;
6+
using Stryker.Core.ProjectComponents;
67
using Xunit;
78

89
namespace Stryker.Core.UnitTest.Options
@@ -23,11 +24,11 @@ public class FilePatternTests : TestBase
2324
public void IsMatch_should_match_glob_pattern(string file, string glob, bool isMatch)
2425
{
2526
// Arrange
26-
var textSpan = new TextSpan(0, 1);
27-
var sut = new FilePattern(Glob.Parse(glob), false, new[] { textSpan });
27+
var mutantSpan = new MutantSpan(0, 1);
28+
var pattern = new FilePattern(Glob.Parse(glob), false, new[] { mutantSpan });
2829

2930
// Act
30-
var result = sut.IsMatch(file, textSpan);
31+
var result = new CsharpFileLeaf().IsMatch(pattern, file, mutantSpan);
3132

3233
// Assert
3334
result.ShouldBe(isMatch);
@@ -45,10 +46,11 @@ public void IsMatch_should_match_glob_pattern(string file, string glob, bool isM
4546
public void IsMatch_should_match_textSpans(string spanPattern, int spanStart, int spanEnd, bool isMatch)
4647
{
4748
// Arrange
48-
var sut = FilePattern.Parse("*.*" + spanPattern);
49+
var mutantSpan = new MutantSpan(spanStart, spanEnd);
50+
var pattern = new CsharpFileLeaf().Parse(new ExcludableString("*.*" + spanPattern));
4951

5052
// Act
51-
var result = sut.IsMatch($"test.cs", TextSpan.FromBounds(spanStart, spanEnd));
53+
var result = new CsharpFileLeaf().IsMatch(pattern, mutantSpan);
5254

5355
// Assert
5456
result.ShouldBe(isMatch);
@@ -62,17 +64,17 @@ public void IsMatch_should_match_textSpans(string spanPattern, int spanStart, in
6264
public void Parse_should_parse_correctly(string spanPattern, string glob, bool isExclude, int[] spans)
6365
{
6466
// Arrange
65-
var textSpans = Enumerable.Range(0, spans.Length)
67+
var mutantSpans = Enumerable.Range(0, spans.Length)
6668
.GroupBy(i => Math.Floor(i / 2d))
67-
.Select(x => TextSpan.FromBounds(spans[x.First()], spans[x.Skip(1).First()]));
69+
.Select(x => new MutantSpan(spans[x.First()], spans[x.Skip(1).First()]));
6870

6971
// Act
70-
var result = FilePattern.Parse(spanPattern);
72+
var result = new CsharpFileLeaf().Parse(new ExcludableString(spanPattern));
7173

7274
// Assert
7375
result.Glob.ToString().ShouldBe(FilePathUtils.NormalizePathSeparators(glob));
7476
result.IsExclude.ShouldBe(isExclude);
75-
result.TextSpans.SequenceEqual(textSpans).ShouldBe(true);
77+
result.MutantSpans.SequenceEqual(mutantSpans).ShouldBe(true);
7678
}
7779
}
7880
}

src/Stryker.Core/Stryker.Core.UnitTest/Options/Inputs/DiffIgnoreChangesInputTests.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Linq;
22
using Shouldly;
33
using Stryker.Core.Options.Inputs;
4+
using Stryker.Core.ProjectComponents;
45
using Xunit;
56

67
namespace Stryker.Core.UnitTest.Options.Inputs
@@ -24,7 +25,7 @@ public void ShouldAcceptGlob()
2425
{
2526
var target = new DiffIgnoreChangesInput { SuppliedInput = new[] { "*" } };
2627

27-
var result = target.Validate();
28+
var result = new SimpleFileLeaf(target.Validate()).Patterns;
2829

2930
result.ShouldHaveSingleItem().Glob.ToString().ShouldBe("*");
3031
}
@@ -34,7 +35,7 @@ public void ShouldParseAll()
3435
{
3536
var target = new DiffIgnoreChangesInput { SuppliedInput = new[] { "*", "MyFile.cs" } };
3637

37-
var result = target.Validate();
38+
var result = new SimpleFileLeaf(target.Validate()).Patterns;
3839

3940
result.Count().ShouldBe(2);
4041

src/Stryker.Core/Stryker.Core.UnitTest/Options/Inputs/MutateInputTests.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Linq;
33
using Shouldly;
44
using Stryker.Core.Options.Inputs;
5+
using Stryker.Core.ProjectComponents;
56
using Xunit;
67

78
namespace Stryker.Core.UnitTest.Options.Inputs
@@ -24,7 +25,7 @@ public void ShouldHaveDefault()
2425
{
2526
var target = new MutateInput { SuppliedInput = new string[] { } };
2627

27-
var result = target.Validate();
28+
var result = new SimpleFileLeaf(target.Validate()).Patterns;
2829

2930
var item = result.ShouldHaveSingleItem();
3031
item.Glob.ToString().ShouldBe(Path.Combine("**", "*"));
@@ -36,7 +37,7 @@ public void ShouldReturnFiles()
3637
{
3738
var target = new MutateInput { SuppliedInput = new[] { Path.Combine("**", "*.cs") } };
3839

39-
var result = target.Validate();
40+
var result = new SimpleFileLeaf(target.Validate()).Patterns;
4041

4142
var item = result.ShouldHaveSingleItem();
4243
item.Glob.ToString().ShouldBe(Path.Combine("**", "*.cs"));
@@ -48,14 +49,13 @@ public void ShouldExcludeAll()
4849
{
4950
var target = new MutateInput { SuppliedInput = new[] { "!" + Path.Combine("**", "Test.cs") } };
5051

51-
var result = target.Validate();
52+
var result = new SimpleFileLeaf(target.Validate()).Patterns;
5253

5354
result.Count().ShouldBe(2);
5455
result.First().Glob.ToString().ShouldBe(Path.Combine("**", "Test.cs"));
5556
result.First().IsExclude.ShouldBeTrue();
5657
result.Last().Glob.ToString().ShouldBe(Path.Combine("**", "*"));
5758
result.Last().IsExclude.ShouldBeFalse();
5859
}
59-
6060
}
6161
}

src/Stryker.Core/Stryker.Core.UnitTest/Options/StrykerOptionsTests.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Shouldly;
66
using Stryker.Core.Mutators;
77
using Stryker.Core.Options;
8+
using Stryker.Core.ProjectComponents;
89
using Xunit;
910

1011
namespace Stryker.Core.UnitTest.Options
@@ -26,7 +27,7 @@ public void ShouldCopyValues()
2627
DashboardUrl = "url",
2728
DevMode = true,
2829
Since = true,
29-
DiffIgnoreChanges = new[] { new FilePattern(Glob.Parse("**"), true, null) },
30+
DiffIgnoreChanges = new[] { new ExcludableString("**") },
3031
ExcludedMutations = new[] { Mutator.Bitwise },
3132
FallbackVersion = "main",
3233
IgnoredMethods = new[] { new Regex("") },
@@ -37,7 +38,7 @@ public void ShouldCopyValues()
3738
LogToFile = true
3839
},
3940
ModuleName = "module",
40-
Mutate = new[] { new FilePattern(Glob.Parse("**"), true, null) },
41+
Mutate = new[] { new ExcludableString("**") },
4142
MutationLevel = MutationLevel.Complete,
4243
OptimizationMode = OptimizationModes.DisableBail,
4344
OutputPath = "output",

src/Stryker.Core/Stryker.Core.UnitTest/ProjectComponents/ProjectComponentExtensionsTests.cs src/Stryker.Core/Stryker.Core.UnitTest/ProjectComponents/TextSpanHelperTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
using Microsoft.CodeAnalysis.Text;
22
using Shouldly;
3-
using Stryker.Core.ProjectComponents;
3+
using Stryker.Core.Helpers;
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
77
using Xunit;
88

99
namespace Stryker.Core.UnitTest.ProjectComponents
1010
{
11-
public class ProjectComponentExtensionsTests : TestBase
11+
public class TextSpanHelperTests : TestBase
1212
{
1313
[Theory]
1414
[InlineData(new int[0], new int[0])]

src/Stryker.Core/Stryker.Core/DiffProviders/GitDiffProvider.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Stryker.Core.Exceptions;
77
using Stryker.Core.Mutants;
88
using Stryker.Core.Options;
9+
using Stryker.Core.ProjectComponents;
910

1011
namespace Stryker.Core.DiffProviders
1112
{
@@ -71,7 +72,7 @@ public DiffResult ScanDiff()
7172

7273
private void RemoveFilteredOutFiles(DiffResult diffResult)
7374
{
74-
foreach (var glob in _options.DiffIgnoreChanges.Select(d => d.Glob))
75+
foreach (var glob in new SimpleFileLeaf(_options.DiffIgnoreChanges).Patterns.Select(d => d.Glob))
7576
{
7677
diffResult.ChangedSourceFiles = diffResult.ChangedSourceFiles.Where(diffResultFile => !glob.IsMatch(diffResultFile)).ToList();
7778
diffResult.ChangedTestFiles = diffResult.ChangedTestFiles.Where(diffResultFile => !glob.IsMatch(diffResultFile)).ToList();

src/Stryker.Core/Stryker.Core/FilePattern.cs

+7-84
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,22 @@
11
using DotNet.Globbing;
2-
using Microsoft.CodeAnalysis.Text;
3-
using Stryker.Core.ProjectComponents;
42
using System;
53
using System.Collections.Generic;
64
using System.Linq;
7-
using System.Text.RegularExpressions;
85

96
namespace Stryker.Core
107
{
8+
public record MutantSpan(int Start, int End);
9+
1110
/// <summary>
1211
/// Contains information about which files and which parts of a file should be in- or excluded.
1312
/// </summary>
1413
public sealed class FilePattern : IEquatable<FilePattern>
1514
{
16-
private static readonly Regex _textSpanGroupRegex = new Regex("(\\{(\\d+)\\.\\.(\\d+)\\})+$");
17-
private static readonly Regex _textSpanRegex = new Regex("\\{(\\d+)\\.\\.(\\d+)\\}");
18-
private static readonly TextSpan _textSpanMaxValue = new TextSpan(0, int.MaxValue);
19-
20-
public FilePattern(Glob glob, bool isExclude, IReadOnlyCollection<TextSpan> textSpans)
15+
public FilePattern(Glob glob, bool isExclude, IReadOnlyCollection<MutantSpan> mutantSpans)
2116
{
2217
Glob = glob;
2318
IsExclude = isExclude;
24-
TextSpans = textSpans;
19+
MutantSpans = mutantSpans;
2520
}
2621

2722
/// <summary>
@@ -37,81 +32,9 @@ public FilePattern(Glob glob, bool isExclude, IReadOnlyCollection<TextSpan> text
3732
/// <summary>
3833
/// Gets the the text spans of the file this pattern matches.
3934
/// </summary>
40-
public IReadOnlyCollection<TextSpan> TextSpans { get; }
41-
42-
/// <summary>
43-
/// Parses a given file pattern string.
44-
/// Format: (!)&lt;glob&gt;({&lt;spanStart&gt;..&lt;spanEnd&gt;})*
45-
/// </summary>
46-
/// <param name="pattern">The pattern to parse.</param>
47-
/// <returns>The <see cref="FilePattern"/></returns>
48-
public static FilePattern Parse(string pattern) => Parse(pattern, spansEnabled: true);
49-
50-
/// <summary>
51-
/// Parses a given file pattern string.
52-
/// Format: (!)&lt;glob&gt;({&lt;spanStart&gt;..&lt;spanEnd&gt;})*
53-
/// </summary>
54-
/// <param name="pattern">The pattern to parse.</param>
55-
/// <param name="spansEnabled">Enable or disable span parsing.</param>
56-
/// <returns>The <see cref="FilePattern"/></returns>
57-
public static FilePattern Parse(string pattern, bool spansEnabled)
58-
{
59-
var exclude = false;
60-
IReadOnlyCollection<TextSpan> textSpans;
61-
62-
if (pattern.StartsWith('!'))
63-
{
64-
exclude = true;
65-
pattern = pattern[1..];
66-
}
67-
68-
var textSpanGroupMatch = _textSpanGroupRegex.Match(pattern);
69-
if (!spansEnabled || !textSpanGroupMatch.Success)
70-
{
71-
// If there are no spans specified, we add one that will cover the whole file.
72-
textSpans = new[] { _textSpanMaxValue };
73-
}
74-
else
75-
{
76-
// If we have one ore more spans we parse them.
77-
var textSpansMatches = _textSpanRegex.Matches(textSpanGroupMatch.Value);
78-
textSpans = textSpansMatches
79-
.Select(x => TextSpan.FromBounds(int.Parse(x.Groups[1].Value), int.Parse(x.Groups[2].Value)))
80-
.Reduce()
81-
.ToList();
82-
83-
pattern = pattern.Substring(0, pattern.Length - textSpanGroupMatch.Length);
84-
}
85-
86-
var glob = Glob.Parse(FilePathUtils.NormalizePathSeparators(pattern));
87-
88-
return new FilePattern(glob, exclude, textSpans);
89-
}
90-
91-
/// <summary>
92-
/// Checks whether a given file path and span matches the current file pattern.
93-
/// </summary>
94-
/// <param name="filePath">The full file path.</param>
95-
/// <param name="textSpan">The span of the text to check.</param>
96-
/// <returns>True if the file and span matches the pattern.</returns>
97-
public bool IsMatch(string filePath, TextSpan textSpan)
98-
{
99-
// Check if the file path is matched.
100-
if (!Glob.IsMatch(FilePathUtils.NormalizePathSeparators(filePath)))
101-
{
102-
return false;
103-
}
104-
105-
// Check if any span fully contains the specified span
106-
if (TextSpans.Any(span => span.Contains(textSpan)))
107-
{
108-
return true;
109-
}
110-
111-
return false;
112-
}
35+
public IReadOnlyCollection<MutantSpan> MutantSpans { get; }
11336

114-
public bool Equals(FilePattern other) => Glob.ToString() == other.Glob.ToString() && IsExclude == other.IsExclude && TextSpans.SequenceEqual(other.TextSpans);
37+
public bool Equals(FilePattern other) => Glob.ToString() == other.Glob.ToString() && IsExclude == other.IsExclude && MutantSpans.SequenceEqual(other.MutantSpans);
11538

11639
public override bool Equals(object obj)
11740
{
@@ -139,7 +62,7 @@ public override int GetHashCode()
13962
{
14063
var hashCode = Glob != null ? Glob.GetHashCode() : 0;
14164
hashCode = (hashCode * 397) ^ IsExclude.GetHashCode();
142-
hashCode = (hashCode * 397) ^ (TextSpans != null ? UncheckedSum(TextSpans.Select(t => t.GetHashCode())) : 0);
65+
hashCode = (hashCode * 397) ^ (MutantSpans != null ? UncheckedSum(MutantSpans.Select(t => t.GetHashCode())) : 0);
14366
return hashCode;
14467
}
14568

0 commit comments

Comments
 (0)