|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.DotNet.Cli.Utils;
using Microsoft.VisualStudio.SolutionPersistence;
using Microsoft.VisualStudio.SolutionPersistence.Model;
using Microsoft.VisualStudio.SolutionPersistence.Serializer;
namespace Microsoft.DotNet.Cli.Sln.Remove.Tests
{
public class GivenDotnetSlnRemove : SdkTest
{
private Func<string, string> HelpText = (defaultVal) => $@"Description:
Remove one or more projects from a solution file.
Usage:
dotnet solution <SLN_FILE> remove [<PROJECT_PATH>...] [options]
Arguments:
<SLN_FILE> The solution file to operate on. If not specified, the command will search the current directory for one. [default: {PathUtility.EnsureTrailingSlash(defaultVal)}]
<PROJECT_PATH> The project paths or names to remove from the solution.
Options:
-?, -h, --help Show command line help.";
public GivenDotnetSlnRemove(ITestOutputHelper log) : base(log)
{
}
[Theory]
[InlineData("sln", "--help")]
[InlineData("sln", "-h")]
[InlineData("solution", "--help")]
[InlineData("solution", "-h")]
public void WhenHelpOptionIsPassedItPrintsUsage(string solutionCommand, string helpArg)
{
var cmd = new DotnetCommand(Log)
.Execute(solutionCommand, "remove", helpArg);
cmd.Should().Pass();
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized(HelpText(Directory.GetCurrentDirectory()));
}
[Theory]
[InlineData("sln")]
[InlineData("solution")]
public void WhenTooManyArgumentsArePassedItPrintsError(string solutionCommand)
{
var cmd = new DotnetCommand(Log)
.Execute(solutionCommand, "one.sln", "two.sln", "three.slnx", "remove");
cmd.Should().Fail();
cmd.StdErr.Should().BeVisuallyEquivalentTo($@"{string.Format(CliStrings.UnrecognizedCommandOrArgument, "two.sln")}
{string.Format(CliStrings.UnrecognizedCommandOrArgument, "three.slnx")}");
}
[Theory]
[InlineData("sln", "")]
[InlineData("sln", "unknownCommandName")]
[InlineData("solution", "")]
[InlineData("solution", "unknownCommandName")]
public void WhenNoCommandIsPassedItPrintsError(string solutionCommand, string commandName)
{
var cmd = new DotnetCommand(Log)
.Execute(solutionCommand, commandName);
cmd.Should().Fail();
cmd.StdErr.Should().Be(CliStrings.RequiredCommandNotPassed);
}
[Theory]
[InlineData("sln", "idontexist.sln")]
[InlineData("sln", "idontexist.slnx")]
[InlineData("sln", "ihave?invalidcharacters")]
[InlineData("sln", "ihaveinv@lidcharacters")]
[InlineData("sln", "ihaveinvalid/characters")]
[InlineData("sln", "ihaveinvalidchar\\acters")]
[InlineData("solution", "idontexist.sln")]
[InlineData("solution", "idontexist.slnx")]
[InlineData("solution", "ihave?invalidcharacters")]
[InlineData("solution", "ihaveinv@lidcharacters")]
[InlineData("solution", "ihaveinvalid/characters")]
[InlineData("solution", "ihaveinvalidchar\\acters")]
public void WhenNonExistingSolutionIsPassedItPrintsErrorAndUsage(string solutionCommand, string solutionName)
{
var cmd = new DotnetCommand(Log)
.Execute(solutionCommand, solutionName, "remove", "p.csproj");
cmd.Should().Fail();
cmd.StdErr.Should().Be(string.Format(CliStrings.CouldNotFindSolutionOrDirectory, solutionName));
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized("");
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public void WhenInvalidSolutionIsPassedItPrintsErrorAndUsage(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("InvalidSolution", identifier: $"{solutionCommand}GivenDotnetSlnRemove")
.WithSource()
.Path;
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"InvalidSolution{solutionExtension}", "remove", projectToRemove);
cmd.Should().Fail();
cmd.StdErr.Should().Match(string.Format(CliStrings.InvalidSolutionFormatString, Path.Combine(projectDirectory, $"InvalidSolution{solutionExtension}"), "*"));
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized("");
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public void WhenInvalidSolutionIsFoundRemovePrintsErrorAndUsage(string solutionCommand, string solutionExtension)
{
var projectDirectoryRoot = _testAssetsManager
.CopyTestAsset("InvalidSolution", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var projectDirectory = solutionExtension == ".sln"
? Path.Join(projectDirectoryRoot, "Sln")
: Path.Join(projectDirectoryRoot, "Slnx");
var solutionPath = Path.Combine(projectDirectory, $"InvalidSolution{solutionExtension}");
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", projectToRemove);
cmd.Should().Fail();
cmd.StdErr.Should().Match(string.Format(CliStrings.InvalidSolutionFormatString, solutionPath, "*"));
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized("");
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public void WhenNoProjectIsPassedItPrintsErrorAndUsage(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}GivenDotnetSlnRemove")
.WithSource()
.Path;
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove");
cmd.Should().Fail();
cmd.StdErr.Should().Be(CliStrings.SpecifyAtLeastOneProjectToRemove);
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized("");
}
[Theory]
[InlineData("sln")]
[InlineData("solution")]
public void WhenNoSolutionExistsInTheDirectoryRemovePrintsErrorAndUsage(string solutionCommand)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, "App");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(solutionPath)
.Execute(solutionCommand, "remove", "App.csproj");
cmd.Should().Fail();
cmd.StdErr.Should().Be(string.Format(CliStrings.SolutionDoesNotExist, solutionPath + Path.DirectorySeparatorChar));
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized("");
}
[Theory]
[InlineData("sln")]
[InlineData("solution")]
public void WhenMoreThanOneSolutionExistsInTheDirectoryItPrintsErrorAndUsage(string solutionCommand)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithMultipleSlnFiles", identifier: $"{solutionCommand}GivenDotnetSlnRemove")
.WithSource()
.Path;
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", projectToRemove);
cmd.Should().Fail();
cmd.StdErr.Should().Be(string.Format(CliStrings.MoreThanOneSolutionInDirectory, projectDirectory + Path.DirectorySeparatorChar));
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized("");
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public void WhenPassedAReferenceNotInSlnItPrintsStatus(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndExistingCsprojReferences", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
var contentBefore = File.ReadAllText(solutionPath);
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", "referenceDoesNotExistInSln.csproj");
cmd.Should().Pass();
cmd.StdOut.Should().Be(string.Format(CliStrings.ProjectNotFoundInTheSolution, "referenceDoesNotExistInSln.csproj"));
File.ReadAllText(solutionPath)
.Should().BeVisuallyEquivalentTo(contentBefore);
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public async Task WhenPassedAReferenceItRemovesTheReferenceButNotOtherReferences(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndExistingCsprojReferences", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath) ?? throw new InvalidOperationException($"Unable to get solution serializer for {solutionPath}.");
SolutionModel solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(2);
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", projectToRemove);
cmd.Should().Pass();
cmd.StdOut.Should().Be(string.Format(CliStrings.ProjectRemovedFromTheSolution, projectToRemove));
solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(1);
solution.SolutionProjects.Single().FilePath.Should().Be(Path.Combine("App", "App.csproj"));
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public async Task WhenPassedAReferenceWithoutExtensionItRemovesTheReferenceButNotOtherReferences(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndExistingCsprojReferences", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath) ?? throw new InvalidOperationException($"Unable to get solution serializer for {solutionPath}.");
SolutionModel solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(2);
var projectToRemove = "Lib";
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", projectToRemove);
cmd.Should().Pass();
cmd.StdOut.Should().Be(string.Format(CliStrings.ProjectRemovedFromTheSolution, Path.Combine(projectToRemove, "Lib.csproj")));
solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(1);
solution.SolutionProjects.Single().FilePath.Should().Be(Path.Combine("App", "App.csproj"));
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public void WhenSolutionItemsExistInFolderParentFoldersAreNotRemoved(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("SlnFileWithSolutionItemsInNestedFolders", identifier: $"{solutionCommand}{solutionExtension}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
var projectToRemove = Path.Combine("ConsoleApp1", "ConsoleApp1.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", projectToRemove);
cmd.Should().Pass();
cmd.StdOut.Should().Be(string.Format(CliStrings.ProjectRemovedFromTheSolution, projectToRemove));
var templateContents = GetSolutionFileTemplateContents($"ExpectedSlnContentsAfterRemoveProjectInSolutionWithNestedSolutionItems{solutionExtension}");
File.ReadAllText(solutionPath)
.Should()
.BeVisuallyEquivalentTo(templateContents);
}
[Theory(Skip = "https://github.com/dotnet/sdk/issues/47860")]
[InlineData("sln")]
[InlineData("solution")]
public async Task WhenDuplicateReferencesArePresentItRemovesThemAll(string solutionCommand)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndDuplicateProjectReferences", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, "App.sln");
ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath) ?? throw new InvalidOperationException($"Unable to get solution serializer for {solutionPath}.");
SolutionModel solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(3);
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "App.sln", "remove", projectToRemove);
cmd.Should().Pass();
string outputText = string.Format(CliStrings.ProjectRemovedFromTheSolution, projectToRemove);
outputText += Environment.NewLine + outputText;
cmd.StdOut.Should().BeVisuallyEquivalentTo(outputText);
solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(1);
solution.SolutionProjects.Single().FilePath.Should().Be(Path.Combine("App", "App.csproj"));
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public async Task WhenPassedMultipleReferencesAndOneOfThemDoesNotExistItRemovesTheOneThatExists(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndExistingCsprojReferences", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath) ?? throw new InvalidOperationException($"Unable to get solution serializer for {solutionPath}.");
SolutionModel solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(2);
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", "idontexist.csproj", projectToRemove, "idontexisteither.csproj");
cmd.Should().Pass();
string outputText = $@"{string.Format(CliStrings.ProjectNotFoundInTheSolution, "idontexist.csproj")}
{string.Format(CliStrings.ProjectRemovedFromTheSolution, projectToRemove)}
{string.Format(CliStrings.ProjectNotFoundInTheSolution, "idontexisteither.csproj")}";
cmd.StdOut.Should().BeVisuallyEquivalentTo(outputText);
solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(1);
solution.SolutionProjects.Single().FilePath.Should().Be(Path.Combine("App", "App.csproj"));
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public async Task WhenReferenceIsRemovedBuildConfigsAreAlsoRemoved(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndCsprojToRemove", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath) ?? throw new InvalidOperationException($"Unable to get solution serializer for {solutionPath}.");
SolutionModel solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(2);
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", projectToRemove);
cmd.Should().Pass();
var templateContents = GetSolutionFileTemplateContents($"ExpectedSlnContentsAfterRemove{solutionExtension}");
File.ReadAllText(solutionPath)
.Should().BeVisuallyEquivalentTo(templateContents);
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public async Task WhenDirectoryContainingProjectIsGivenProjectIsRemoved(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndCsprojToRemove", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath) ?? throw new InvalidOperationException($"Unable to get solution serializer for {solutionPath}.");
SolutionModel solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(2);
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", "Lib");
cmd.Should().Pass();
var templateContents = GetSolutionFileTemplateContents($"ExpectedSlnContentsAfterRemove{solutionExtension}");
File.ReadAllText(solutionPath)
.Should().BeVisuallyEquivalentTo(templateContents);
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public void WhenDirectoryContainsNoProjectsItCancelsWholeOperation(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndCsprojToRemove", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var directoryToRemove = "Empty";
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", directoryToRemove);
cmd.Should().Fail();
cmd.StdErr.Should().Be(
string.Format(
CliStrings.CouldNotFindAnyProjectInDirectory,
Path.Combine(projectDirectory, directoryToRemove)));
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized("");
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public void WhenDirectoryContainsMultipleProjectsItCancelsWholeOperation(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndCsprojToRemove", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var directoryToRemove = "Multiple";
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", directoryToRemove);
cmd.Should().Fail();
cmd.StdErr.Should().Be(
string.Format(
CliStrings.MoreThanOneProjectInDirectory,
Path.Combine(projectDirectory, directoryToRemove)));
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized("");
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public async Task WhenReferenceIsRemovedSlnBuilds(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndCsprojToRemove", identifier: $"{solutionCommand}{solutionExtension}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath) ?? throw new InvalidOperationException($"Unable to get solution serializer for {solutionPath}.");
SolutionModel solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(2);
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", projectToRemove);
cmd.Should().Pass();
new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute($"restore", $"App{solutionExtension}")
.Should().Pass();
new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute("build", $"App{solutionExtension}", "--configuration", "Release", "/p:ProduceReferenceAssembly=false")
.Should().Pass();
var reasonString = "should be built in release mode, otherwise it means build configurations are missing from the sln file";
var outputCalculator = OutputPathCalculator.FromProject(Path.Combine(projectDirectory, "App"));
new DirectoryInfo(outputCalculator.GetOutputDirectory(configuration: "Debug")).Should().NotExist(reasonString);
var outputDirectory = new DirectoryInfo(outputCalculator.GetOutputDirectory(configuration: "Release"));
outputDirectory.Should().Exist();
outputDirectory.Should().HaveFile("App.dll");
}
[Theory]
[InlineData("sln")]
[InlineData("solution")]
public void WhenProjectIsRemovedSolutionHasUTF8BOM(string solutionCommand)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndCsprojToRemove", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "App.sln", "remove", projectToRemove);
cmd.Should().Pass();
var preamble = Encoding.UTF8.GetPreamble();
preamble.Length.Should().Be(3);
using (var stream = new FileStream(Path.Combine(projectDirectory, "App.sln"), FileMode.Open))
{
var bytes = new byte[preamble.Length];
#if NET
stream.ReadExactly(bytes, 0, bytes.Length);
#else
int offset = 0;
int count = bytes.Length;
while (count > 0)
{
int read = stream.Read(bytes, offset, count);
if (read <= 0)
{
throw new EndOfStreamException();
}
offset += read;
count -= read;
}
#endif
bytes.Should().BeEquivalentTo(preamble);
}
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public async Task WhenFinalReferenceIsRemovedEmptySectionsAreRemoved(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndCsprojToRemove", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath) ?? throw new InvalidOperationException($"Unable to get solution serializer for {solutionPath}.");
SolutionModel solution = await serializer.OpenAsync(solutionPath, CancellationToken.None);
solution.SolutionProjects.Count.Should().Be(2);
var appPath = Path.Combine("App", "App.csproj");
var libPath = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", libPath, appPath);
cmd.Should().Pass();
var solutionContents = File.ReadAllText(solutionPath);
var templateContents = GetSolutionFileTemplateContents($"ExpectedSlnContentsAfterRemoveAllProjects{solutionExtension}");
solutionContents.Should().BeVisuallyEquivalentTo(templateContents);
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public void WhenNestedProjectIsRemovedItsSolutionFoldersAreRemoved(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndCsprojInSubDirToRemove", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
var projectToRemove = Path.Combine("src", "NotLastProjInSrc", "NotLastProjInSrc.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", projectToRemove);
cmd.Should().Pass();
var templateContents = GetSolutionFileTemplateContents($"ExpectedSlnContentsAfterRemoveNestedProj{solutionExtension}");
File.ReadAllText(solutionPath)
.Should().BeVisuallyEquivalentTo(templateContents);
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public void WhenFinalNestedProjectIsRemovedSolutionFoldersAreRemoved(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndLastCsprojInSubDirToRemove", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
var projectToRemove = Path.Combine("src", "Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", projectToRemove);
cmd.Should().Pass();
var templateContents = GetSolutionFileTemplateContents($"ExpectedSlnContentsAfterRemoveLastNestedProj{solutionExtension}");
File.ReadAllText(solutionPath)
.Should().BeVisuallyEquivalentTo(templateContents);
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public void WhenProjectIsRemovedThenDependenciesOnProjectAreAlsoRemoved(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnProjectDependencyToRemove", identifier: $"{solutionCommand}")
.WithSource()
.Path;
var solutionPath = Path.Combine(projectDirectory, $"App{solutionExtension}");
var projectToRemove = Path.Combine("Second", "Second.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, $"App{solutionExtension}", "remove", projectToRemove);
cmd.Should().Pass();
var templateContents = GetSolutionFileTemplateContents($"ExpectedSlnContentsAfterRemoveProjectWithDependencies{solutionExtension}");
File.ReadAllText(solutionPath)
.Should().BeVisuallyEquivalentTo(templateContents);
}
[Theory]
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
[InlineData("sln", ".slnx")]
[InlineData("solution", ".slnx")]
public void WhenSolutionIsPassedAsProjectItPrintsSuggestionAndUsage(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
.CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"{solutionCommand}{solutionExtension}")
.WithSource()
.Path;
var projectArg = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", $"App{solutionExtension}", projectArg);
cmd.Should().Fail();
cmd.StdErr.Should().BeVisuallyEquivalentTo(
string.Format(CliStrings.SolutionArgumentMisplaced, $"App{solutionExtension}") + Environment.NewLine
+ CliStrings.DidYouMean + Environment.NewLine
+ $" dotnet solution App{solutionExtension} remove {projectArg}"
);
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized("");
}
private string GetSolutionFileTemplateContents(string templateFileName)
{
var templateContentDirectory = _testAssetsManager
.CopyTestAsset("SolutionFilesTemplates", identifier: "SolutionFilesTemplates")
.WithSource()
.Path;
return File.ReadAllText(Path.Join(templateContentDirectory, templateFileName));
}
}
}
|