Skip to content
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
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<PackageVersion Include="Microsoft.Build.Prediction" Version="1.2.18" />
<PackageVersion Include="Microsoft.Build.Locator" Version="1.7.8" />
<PackageVersion Include="Microsoft.VisualStudio.SolutionPersistence" Version="1.0.52" />

<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.12.1" Condition="'$(OS)' == 'Windows_NT'"/>
Expand Down
1 change: 1 addition & 0 deletions src/DotnetAffected.Core/DotnetAffected.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Build.Prediction" />
<PackageReference Include="LibGit2Sharp" />
<PackageReference Include="Microsoft.VisualStudio.SolutionPersistence" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public IEnumerable<string> DiscoverProjects(IDiscoveryOptions options)
}

// When a filtering file is provided, use a specific discoverer based on its path.
if (options.FilterFilePath.EndsWith(".sln"))
if (options.FilterFilePath.EndsWith(".sln") || options.FilterFilePath.EndsWith(".slnx"))
{
return new SolutionFileProjectDiscoverer().DiscoverProjects(options);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
using DotnetAffected.Abstractions;
using Microsoft.Build.Construction;
using Microsoft.VisualStudio.SolutionPersistence.Serializer;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;

namespace DotnetAffected.Core
{
internal class SolutionFileProjectDiscoverer : IProjectDiscoverer
{
public IEnumerable<string> DiscoverProjects(IDiscoveryOptions options)
{
var solution = SolutionFile.Parse(options.FilterFilePath);
// It should not be possible for this to be null based on call paths - but this makes the warning go away
ArgumentNullException.ThrowIfNull(options.FilterFilePath);

return solution.ProjectsInOrder
.Where(x => x.ProjectType != SolutionProjectType.SolutionFolder)
.Select(x => x.AbsolutePath)
var serializer = SolutionSerializers.GetSerializerByMoniker(options.FilterFilePath);

if (serializer is null) throw new NotSupportedException($"Filtering by {options.FilterFilePath} is not supported");

var solution = serializer.OpenAsync(options.FilterFilePath, CancellationToken.None).GetAwaiter().GetResult();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would probably be better to go through the full async/await way that getting the awaiter and result


var solutionDir = Path.GetDirectoryName(options.FilterFilePath);

return solution.SolutionProjects
.Select(x => Path.IsPathRooted(x.FilePath) ? x.FilePath : Path.Join(solutionDir, x.FilePath))
.ToArray();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/dotnet-affected/Commands/AffectedGlobalOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal static class AffectedGlobalOptions
{
"--filter-file-path"
},
description: "Path to a filter file (.sln) used to discover projects that may be affected.\n" +
description: "Path to a filter file (.sln, .slnx) used to discover projects that may be affected.\n" +
"When omitted, will search for project files inside --repository-path.");

public static readonly Option<bool> VerboseOption = new(aliases: new[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using DotnetAffected.Testing.Utils;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace DotnetAffected.Core.Tests
{
/// <summary>
/// Tests for detecting changed projects when using a SolutionFile to filter.
/// This should cover all tests where filtering should be applied
/// </summary>
public class ChangeDetectionUsingXmlSolutionTests
: BaseDotnetAffectedTest
{
private readonly string _solutionPath = "test-solution.slnx";

protected override AffectedOptions Options => new AffectedOptions(
this.Repository.Path,
Path.Combine(this.Repository.Path, this._solutionPath));

[Fact]
public async Task When_project_inside_solution_has_changes_project_should_have_changed()
{
// Create a project
var projectName = "InventoryManagement";
var msBuildProject = this.Repository.CreateCsProject(projectName);

// Create a solution which includes the project
await this.Repository.CreateXmlSolutionAsync(_solutionPath, msBuildProject.FullPath);

Assert.Single(AffectedSummary.ProjectsWithChangedFiles);
Assert.Empty(AffectedSummary.AffectedProjects);

var projectInfo = AffectedSummary.ProjectsWithChangedFiles.Single();
Assert.Equal(projectName, projectInfo.GetProjectName());
Assert.Equal(msBuildProject.FullPath, projectInfo.GetFullPath());
}

[Fact]
public async Task When_project_inside_solution_should_ignore_changes_outside_solution()
{
// Create a project
var projectName = "InventoryManagement";
var msBuildProject = this.Repository.CreateCsProject(projectName);

// Create a solution which includes the project
await this.Repository.CreateXmlSolutionAsync(_solutionPath, msBuildProject.FullPath);

// Create a project that is outside the solution
var outsiderproject = "OutsiderProject";
this.Repository.CreateCsProject(outsiderproject);

Assert.Single(AffectedSummary.ProjectsWithChangedFiles);
Assert.Empty(AffectedSummary.AffectedProjects);

var projectInfo = AffectedSummary.ProjectsWithChangedFiles.Single();
Assert.Equal(projectName, projectInfo.GetProjectName());
Assert.Equal(msBuildProject.FullPath, projectInfo.GetFullPath());
}

[Fact]
public async Task When_project_outside_solution_has_changed_nothing_should_be_affected()
{
// Create a project
var projectName = "InventoryManagement";
var msBuildProject = this.Repository.CreateCsProject(projectName);

// Create a solution which includes the project
await this.Repository.CreateXmlSolutionAsync(_solutionPath, msBuildProject.FullPath);

// Commit so there are no changes
this.Repository.StageAndCommit();

// Create a project that is outside the solution
var outsiderName = "OutsiderProject";
this.Repository.CreateCsProject(outsiderName);

Assert.Empty(AffectedSummary.AffectedProjects);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,20 @@ public static async Task CreateSolutionAsync(
await File.WriteAllTextAsync(solutionPath, solutionContents);
}

public static async Task CreateXmlSolutionAsync(
this TemporaryRepository repo,
string solutionName,
params string[] projectPaths)
{
var i = 0;
var solutionContents = new SolutionFileBuilder { Projects = projectPaths.ToDictionary(p => i++.ToString()) }
.BuildXmlSolution();

var solutionPath = Path.Combine(repo.Path, solutionName);

await File.WriteAllTextAsync(solutionPath, solutionContents);
}

public static Task CreateTraversalProjectAsync(
this TemporaryRepository repo,
string traversalProjectPath,
Expand Down
21 changes: 21 additions & 0 deletions test/DotnetAffected.Testing.Utils/SolutionFileBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,26 @@ public string BuildSolution()

return sb.ToString();
}

public string BuildXmlSolution()
{
var sb = new StringBuilder();
sb.AppendLine(@"<Solution>");
sb.AppendLine(@" <Configurations>");
sb.AppendLine(@" <Platform Name=""Any CPU"" />");
sb.AppendLine(@" <Platform Name=""x64"" />");
sb.AppendLine(@" <Platform Name=""x86"" />");
sb.AppendLine(@" </Configurations>");

foreach (var project in Projects.Values)
{
sb.AppendLine(@$" <Project Path=""{project}"" />");

}

sb.AppendLine(@"</Solution>");

return sb.ToString();
}
}
}
Loading