diff --git a/TestAssets/TestProjects/AppWithCorruptedLaunchSettings/AppWithCorruptedLaunchSettings.csproj b/TestAssets/TestProjects/AppWithCorruptedLaunchSettings/AppWithCorruptedLaunchSettings.csproj new file mode 100644 index 000000000..45a048433 --- /dev/null +++ b/TestAssets/TestProjects/AppWithCorruptedLaunchSettings/AppWithCorruptedLaunchSettings.csproj @@ -0,0 +1,9 @@ + + + + + Exe + netcoreapp2.0 + win7-x64;win7-x86;osx.10.12-x64;ubuntu.14.04-x64;ubuntu.16.04-x64;ubuntu.16.10-x64;centos.7-x64;rhel.7-x64;debian.8-x64;fedora.24-x64;opensuse.42.1-x64 + + diff --git a/TestAssets/TestProjects/AppWithCorruptedLaunchSettings/Program.cs b/TestAssets/TestProjects/AppWithCorruptedLaunchSettings/Program.cs new file mode 100644 index 000000000..042e99826 --- /dev/null +++ b/TestAssets/TestProjects/AppWithCorruptedLaunchSettings/Program.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace MSBuildTestApp +{ + public class Program + { + public static void Main(string[] args) + { + if (args.Length > 0) + { + Console.WriteLine("echo args:" + string.Join(";", args)); + } + string message = Environment.GetEnvironmentVariable("Message"); + + if (string.IsNullOrEmpty(message)) + { + message = "(NO MESSAGE)"; + } + + Console.WriteLine(message); + } + } +} diff --git a/TestAssets/TestProjects/AppWithCorruptedLaunchSettings/Properties/launchSettings.json b/TestAssets/TestProjects/AppWithCorruptedLaunchSettings/Properties/launchSettings.json new file mode 100644 index 000000000..4307944ab --- /dev/null +++ b/TestAssets/TestProjects/AppWithCorruptedLaunchSettings/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:49850/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "First": { + "commandName": "Project", + "environmentVariables": [ ] + }, + "Second": { + "commandName": "Project", + "environmentVariables": { + "Message": "Second" + } + } + } +} \ No newline at end of file diff --git a/TestAssets/TestProjects/AppWithLaunchSettings/AppWithLaunchSettings.csproj b/TestAssets/TestProjects/AppWithLaunchSettings/AppWithLaunchSettings.csproj new file mode 100644 index 000000000..45a048433 --- /dev/null +++ b/TestAssets/TestProjects/AppWithLaunchSettings/AppWithLaunchSettings.csproj @@ -0,0 +1,9 @@ + + + + + Exe + netcoreapp2.0 + win7-x64;win7-x86;osx.10.12-x64;ubuntu.14.04-x64;ubuntu.16.04-x64;ubuntu.16.10-x64;centos.7-x64;rhel.7-x64;debian.8-x64;fedora.24-x64;opensuse.42.1-x64 + + diff --git a/TestAssets/TestProjects/AppWithLaunchSettings/Program.cs b/TestAssets/TestProjects/AppWithLaunchSettings/Program.cs new file mode 100644 index 000000000..042e99826 --- /dev/null +++ b/TestAssets/TestProjects/AppWithLaunchSettings/Program.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace MSBuildTestApp +{ + public class Program + { + public static void Main(string[] args) + { + if (args.Length > 0) + { + Console.WriteLine("echo args:" + string.Join(";", args)); + } + string message = Environment.GetEnvironmentVariable("Message"); + + if (string.IsNullOrEmpty(message)) + { + message = "(NO MESSAGE)"; + } + + Console.WriteLine(message); + } + } +} diff --git a/TestAssets/TestProjects/AppWithLaunchSettings/Properties/launchSettings.json b/TestAssets/TestProjects/AppWithLaunchSettings/Properties/launchSettings.json new file mode 100644 index 000000000..615a345f2 --- /dev/null +++ b/TestAssets/TestProjects/AppWithLaunchSettings/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:49850/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "First": { + "commandName": "Project", + "environmentVariables": { + "Message": "First" + } + }, + "Second": { + "commandName": "Project", + "environmentVariables": { + "Message": "Second" + } + } + } +} \ No newline at end of file diff --git a/TestAssets/TestProjects/AppWithLaunchSettingsNoDefault/AppWithLaunchSettingsNoDefault.csproj b/TestAssets/TestProjects/AppWithLaunchSettingsNoDefault/AppWithLaunchSettingsNoDefault.csproj new file mode 100644 index 000000000..45a048433 --- /dev/null +++ b/TestAssets/TestProjects/AppWithLaunchSettingsNoDefault/AppWithLaunchSettingsNoDefault.csproj @@ -0,0 +1,9 @@ + + + + + Exe + netcoreapp2.0 + win7-x64;win7-x86;osx.10.12-x64;ubuntu.14.04-x64;ubuntu.16.04-x64;ubuntu.16.10-x64;centos.7-x64;rhel.7-x64;debian.8-x64;fedora.24-x64;opensuse.42.1-x64 + + diff --git a/TestAssets/TestProjects/AppWithLaunchSettingsNoDefault/Program.cs b/TestAssets/TestProjects/AppWithLaunchSettingsNoDefault/Program.cs new file mode 100644 index 000000000..042e99826 --- /dev/null +++ b/TestAssets/TestProjects/AppWithLaunchSettingsNoDefault/Program.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace MSBuildTestApp +{ + public class Program + { + public static void Main(string[] args) + { + if (args.Length > 0) + { + Console.WriteLine("echo args:" + string.Join(";", args)); + } + string message = Environment.GetEnvironmentVariable("Message"); + + if (string.IsNullOrEmpty(message)) + { + message = "(NO MESSAGE)"; + } + + Console.WriteLine(message); + } + } +} diff --git a/TestAssets/TestProjects/AppWithLaunchSettingsNoDefault/Properties/launchSettings.json b/TestAssets/TestProjects/AppWithLaunchSettingsNoDefault/Properties/launchSettings.json new file mode 100644 index 000000000..010893fd5 --- /dev/null +++ b/TestAssets/TestProjects/AppWithLaunchSettingsNoDefault/Properties/launchSettings.json @@ -0,0 +1,19 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:49850/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-run/LaunchSettings/ILaunchSettingsProvider.cs b/src/dotnet/commands/dotnet-run/LaunchSettings/ILaunchSettingsProvider.cs new file mode 100644 index 000000000..64840872e --- /dev/null +++ b/src/dotnet/commands/dotnet-run/LaunchSettings/ILaunchSettingsProvider.cs @@ -0,0 +1,13 @@ +using Microsoft.DotNet.Cli.Utils; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.Tools.Run.LaunchSettings +{ + public interface ILaunchSettingsProvider + { + string CommandName { get; } + + LaunchSettingsApplyResult TryApplySettings(JObject document, JObject model, ref ICommand command); + } + +} diff --git a/src/dotnet/commands/dotnet-run/LaunchSettings/LaunchSettingsApplyResult.cs b/src/dotnet/commands/dotnet-run/LaunchSettings/LaunchSettingsApplyResult.cs new file mode 100644 index 000000000..04b9dca3b --- /dev/null +++ b/src/dotnet/commands/dotnet-run/LaunchSettings/LaunchSettingsApplyResult.cs @@ -0,0 +1,18 @@ +namespace Microsoft.DotNet.Tools.Run.LaunchSettings +{ + public class LaunchSettingsApplyResult + { + public LaunchSettingsApplyResult(bool success, string failureReason, string runAfterLaunch = null) + { + Success = success; + FailureReason = failureReason; + RunAfterLaunch = runAfterLaunch; + } + + public bool Success { get; } + + public string FailureReason { get; } + + public string RunAfterLaunch { get; } + } +} diff --git a/src/dotnet/commands/dotnet-run/LaunchSettings/LaunchSettingsManager.cs b/src/dotnet/commands/dotnet-run/LaunchSettings/LaunchSettingsManager.cs new file mode 100644 index 000000000..0fbb7f777 --- /dev/null +++ b/src/dotnet/commands/dotnet-run/LaunchSettings/LaunchSettingsManager.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.DotNet.Cli.Utils; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.Tools.Run.LaunchSettings +{ + internal class LaunchSettingsManager + { + private const string ProfilesKey = "profiles"; + private const string CommandNameKey = "commandName"; + private const string DefaultProfileCommandName = "Project"; + private static IReadOnlyDictionary _providers; + + static LaunchSettingsManager() + { + _providers = new Dictionary + { + { ProjectLaunchSettingsProvider.CommandNameValue, new ProjectLaunchSettingsProvider() } + }; + } + + public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSettingsJsonContents, ref ICommand command, string profileName = null) + { + try + { + var model = JObject.Parse(launchSettingsJsonContents); + var profilesObject = model[ProfilesKey] as JObject; + + if (profilesObject == null) + { + return new LaunchSettingsApplyResult(false, LocalizableStrings.LaunchProfilesCollectionIsNotAJsonObject); + } + + JObject profileObject; + if (profileName == null) + { + profileObject = profilesObject + .Properties() + .FirstOrDefault(IsDefaultProfileType)?.Value as JObject; + } + else + { + profileObject = profilesObject[profileName] as JObject; + + if (profileObject == null) + { + return new LaunchSettingsApplyResult(false, LocalizableStrings.LaunchProfileIsNotAJsonObject); + } + } + + if (profileObject == null) + { + foreach (var prop in profilesObject.Properties()) + { + var profile = prop.Value as JObject; + + if (profile != null) + { + var cmdName = profile[CommandNameKey]?.Value(); + if (_providers.ContainsKey(cmdName)) + { + profileObject = profile; + break; + } + } + } + } + + var commandName = profileObject?[CommandNameKey]?.Value(); + + if (profileObject == null) + { + return new LaunchSettingsApplyResult(false, LocalizableStrings.UsableLaunchProfileCannotBeLocated); + } + + if (!TryLocateHandler(commandName, out ILaunchSettingsProvider provider)) + { + return new LaunchSettingsApplyResult(false, string.Format(LocalizableStrings.LaunchProfileHandlerCannotBeLocated, commandName)); + } + + return provider.TryApplySettings(model, profileObject, ref command); + } + catch (JsonException ex) + { + return new LaunchSettingsApplyResult(false, string.Format(LocalizableStrings.DeserializationExceptionMessage, ex.Message)); + } + } + + private static bool TryLocateHandler(string commandName, out ILaunchSettingsProvider provider) + { + return _providers.TryGetValue(commandName, out provider); + } + + private static bool IsDefaultProfileType(JProperty profileProperty) + { + JObject profile = profileProperty.Value as JObject; + var commandName = profile?[CommandNameKey]?.Value(); + return string.Equals(commandName, DefaultProfileCommandName, StringComparison.Ordinal); + } + } +} diff --git a/src/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsProvider.cs b/src/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsProvider.cs new file mode 100644 index 000000000..780870494 --- /dev/null +++ b/src/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsProvider.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using Microsoft.DotNet.Cli.Utils; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.Tools.Run.LaunchSettings +{ + public class ProjectLaunchSettingsProvider : ILaunchSettingsProvider + { + public static readonly string CommandNameValue = "Project"; + + public string CommandName => CommandNameValue; + + public LaunchSettingsApplyResult TryApplySettings(JObject document, JObject model, ref ICommand command) + { + var config = model.ToObject(); + + //For now, ignore everything but the environment variables section + + foreach (var entry in config.EnvironmentVariables) + { + string value = Environment.ExpandEnvironmentVariables(entry.Value); + //NOTE: MSBuild variables are not expanded like they are in VS + command.EnvironmentVariable(entry.Key, value); + } + + if (!string.IsNullOrEmpty(config.ApplicationUrl)) + { + command.EnvironmentVariable("ASPNETCORE_URLS", config.ApplicationUrl); + } + + return new LaunchSettingsApplyResult(true, null, config.LaunchUrl); + } + + private class ProjectLaunchSettingsModel + { + public ProjectLaunchSettingsModel() + { + EnvironmentVariables = new Dictionary(); + } + + public string CommandLineArgs { get; set; } + + public bool LaunchBrowser { get; set; } + + public string LaunchUrl { get; set; } + + public string ApplicationUrl { get; set; } + + public Dictionary EnvironmentVariables { get; } + } + } +} diff --git a/src/dotnet/commands/dotnet-run/LocalizableStrings.cs b/src/dotnet/commands/dotnet-run/LocalizableStrings.cs index 4cdfd5217..bf700eece 100644 --- a/src/dotnet/commands/dotnet-run/LocalizableStrings.cs +++ b/src/dotnet/commands/dotnet-run/LocalizableStrings.cs @@ -17,6 +17,10 @@ namespace Microsoft.DotNet.Tools.Run public const string CommandOptionProjectDescription = "The path to the project file to run (defaults to the current directory if there is only one project)."; + public const string CommandOptionLaunchProfileDescription = "The name of the launch profile (if any) to use when launching the application."; + + public const string CommandOptionNoLaunchProfileDescription = "Do not attempt to use launchSettings.json to configure the application."; + public const string RunCommandException = "The build failed. Please fix the build errors and run again."; public const string RunCommandExceptionUnableToRunSpecifyFramework = "Unable to run your project\nYour project targets multiple frameworks. Please specify which framework to run using '{0}'."; @@ -28,5 +32,25 @@ namespace Microsoft.DotNet.Tools.Run public const string RunCommandExceptionMultipleProjects = "Specify which project file to use because {0} contains more than one project file."; public const string RunCommandAdditionalArgsHelpText = "Arguments passed to the application that is being run."; + + public const string RunCommandExceptionCouldNotLocateALaunchSettingsFile = "The specified launch profile could not be located."; + + public const string RunCommandExceptionCouldNotApplyLaunchSettings = "The launch profile \"{0}\" could not be applied.\n{1}"; + + public const string DefaultLaunchProfileDisplayName = "(Default)"; + + public const string UsingLaunchSettingsFromMessage = "Using launch settings from {0}..."; + + public const string LaunchProfileIsNotAJsonObject = "Launch profile is not a JSON object."; + + public const string LaunchProfileHandlerCannotBeLocated = "The launch profile type '{0}' is not supported."; + + public const string UsableLaunchProfileCannotBeLocated = "A usable launch profile could not be located."; + + public const string UnexpectedExceptionProcessingLaunchSettings = "An unexpected exception occurred while processing launch settings:\n{0}"; + + public const string LaunchProfilesCollectionIsNotAJsonObject = "The 'profiles' property of the launch settings document is not a JSON object."; + + public const string DeserializationExceptionMessage = "An error was encountered when reading launchSettings.json.\n{0}"; } } diff --git a/src/dotnet/commands/dotnet-run/RunCommand.cs b/src/dotnet/commands/dotnet-run/RunCommand.cs index 5a60765ee..3401f1ac1 100644 --- a/src/dotnet/commands/dotnet-run/RunCommand.cs +++ b/src/dotnet/commands/dotnet-run/RunCommand.cs @@ -8,6 +8,7 @@ using System.Linq; using Microsoft.Build.Evaluation; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Tools.MSBuild; +using Microsoft.DotNet.Tools.Run.LaunchSettings; namespace Microsoft.DotNet.Tools.Run { @@ -22,6 +23,10 @@ namespace Microsoft.DotNet.Tools.Run private List _args; private bool ShouldBuild => !NoBuild; + public string LaunchProfile { get; private set; } + public bool NoLaunchProfile { get; private set; } + private bool UseLaunchProfile => !NoLaunchProfile; + public int Start() { Initialize(); @@ -32,6 +37,12 @@ namespace Microsoft.DotNet.Tools.Run } ICommand runCommand = GetRunCommand(); + int launchSettingsApplicationResult = ApplyLaunchProfileSettingsIfNeeded(ref runCommand); + + if (launchSettingsApplicationResult != 0) + { + return launchSettingsApplicationResult; + } return runCommand .Execute() @@ -42,12 +53,16 @@ namespace Microsoft.DotNet.Tools.Run string framework, bool noBuild, string project, + string launchProfile, + bool noLaunchProfile, IReadOnlyCollection args) { Configuration = configuration; Framework = framework; NoBuild = noBuild; Project = project; + LaunchProfile = launchProfile; + NoLaunchProfile = noLaunchProfile; Args = args; } @@ -55,6 +70,8 @@ namespace Microsoft.DotNet.Tools.Run string framework = null, bool? noBuild = null, string project = null, + string launchProfile = null, + bool? noLaunchProfile = null, IReadOnlyCollection args = null) { return new RunCommand( @@ -62,10 +79,50 @@ namespace Microsoft.DotNet.Tools.Run framework ?? this.Framework, noBuild ?? this.NoBuild, project ?? this.Project, + launchProfile ?? this.LaunchProfile, + noLaunchProfile ?? this.NoLaunchProfile, args ?? this.Args ); } + private int ApplyLaunchProfileSettingsIfNeeded(ref ICommand runCommand) + { + if (UseLaunchProfile) + { + var buildPathContainer = File.Exists(Project) ? Path.GetDirectoryName(Project) : Project; + var launchSettingsPath = Path.Combine(buildPathContainer, "Properties", "launchSettings.json"); + if (File.Exists(launchSettingsPath)) + { + Reporter.Output.WriteLine(string.Format(LocalizableStrings.UsingLaunchSettingsFromMessage, launchSettingsPath)); + string profileName = string.IsNullOrEmpty(LaunchProfile) ? LocalizableStrings.DefaultLaunchProfileDisplayName : LaunchProfile; + + try + { + var launchSettingsFileContents = File.ReadAllText(launchSettingsPath); + var applyResult = LaunchSettingsManager.TryApplyLaunchSettings(launchSettingsFileContents, ref runCommand, LaunchProfile); + if (!applyResult.Success) + { + //Error that the launch profile couldn't be applied + Reporter.Error.WriteLine(string.Format(LocalizableStrings.RunCommandExceptionCouldNotApplyLaunchSettings, profileName, applyResult.FailureReason).Bold().Red()); + } + } + catch (IOException ex) + { + Reporter.Error.WriteLine(string.Format(LocalizableStrings.RunCommandExceptionCouldNotApplyLaunchSettings, profileName).Bold().Red()); + Reporter.Error.WriteLine(ex.Message.Bold().Red()); + return -1; + } + } + else if (!string.IsNullOrEmpty(LaunchProfile)) + { + //Error that the launch profile couldn't be found + Reporter.Error.WriteLine(LocalizableStrings.RunCommandExceptionCouldNotLocateALaunchSettingsFile.Bold().Red()); + } + } + + return 0; + } + private void EnsureProjectIsBuilt() { List buildArgs = new List(); diff --git a/src/dotnet/commands/dotnet-run/RunCommandParser.cs b/src/dotnet/commands/dotnet-run/RunCommandParser.cs index 54f439d79..ec0839c88 100644 --- a/src/dotnet/commands/dotnet-run/RunCommandParser.cs +++ b/src/dotnet/commands/dotnet-run/RunCommandParser.cs @@ -21,6 +21,8 @@ namespace Microsoft.DotNet.Cli framework: o.SingleArgumentOrDefault("--framework"), noBuild: o.HasOption("--no-build"), project: o.SingleArgumentOrDefault("--project"), + launchProfile: o.SingleArgumentOrDefault("--launch-profile"), + noLaunchProfile: o.HasOption("--no-launch-profile"), args: o.Arguments )), options: new[] @@ -32,6 +34,14 @@ namespace Microsoft.DotNet.Cli "-p|--project", LocalizableStrings.CommandOptionProjectDescription, Accept.ExactlyOneArgument()), + Create.Option( + "--launch-profile", + LocalizableStrings.CommandOptionLaunchProfileDescription, + Accept.ExactlyOneArgument()), + Create.Option( + "--no-launch-profile", + LocalizableStrings.CommandOptionNoLaunchProfileDescription, + Accept.NoArguments()), Create.Option( "--no-build", LocalizableStrings.CommandOptionNoBuildDescription, diff --git a/test/dotnet-run.Tests/GivenDotnetRunRunsCsProj.cs b/test/dotnet-run.Tests/GivenDotnetRunRunsCsProj.cs index 8ce1730be..234f890e9 100644 --- a/test/dotnet-run.Tests/GivenDotnetRunRunsCsProj.cs +++ b/test/dotnet-run.Tests/GivenDotnetRunRunsCsProj.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.IO; +using FluentAssertions; using Microsoft.DotNet.Tools.Test.Utilities; using Xunit; @@ -200,5 +201,267 @@ namespace Microsoft.DotNet.Cli.Run.Tests .Pass() .And.HaveStdOutContaining("echo args:foo;bar;baz"); } + + [Fact] + public void ItGivesAnErrorWhenAttemptingToUseALaunchProfileThatDoesNotExistWhenThereIsNoLaunchSettingsFile() + { + var testAppName = "MSBuildTestApp"; + var testInstance = TestAssets.Get(testAppName) + .CreateInstance() + .WithSourceFiles(); + + var testProjectDirectory = testInstance.Root.FullName; + + new RestoreCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute("/p:SkipInvalidConfigurations=true") + .Should().Pass(); + + new BuildCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute() + .Should().Pass(); + + new RunCommand() + .WithWorkingDirectory(testProjectDirectory) + .ExecuteWithCapturedOutput("--launch-profile test") + .Should().Pass() + .And.HaveStdOutContaining("Hello World!") + .And.HaveStdErrContaining("The specified launch profile could not be located."); + } + + [Fact] + public void ItUsesLaunchProfileOfTheSpecifiedName() + { + var testAppName = "AppWithLaunchSettings"; + var testInstance = TestAssets.Get(testAppName) + .CreateInstance() + .WithSourceFiles(); + + var testProjectDirectory = testInstance.Root.FullName; + + new RestoreCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute("/p:SkipInvalidConfigurations=true") + .Should().Pass(); + + new BuildCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute() + .Should().Pass(); + + var cmd = new RunCommand() + .WithWorkingDirectory(testProjectDirectory) + .ExecuteWithCapturedOutput("--launch-profile Second"); + + cmd.Should().Pass() + .And.HaveStdOutContaining("Second"); + + cmd.StdErr.Should().BeEmpty(); + } + + [Fact] + public void ItDefaultsToTheFirstUsableLaunchProfile() + { + var testAppName = "AppWithLaunchSettings"; + var testInstance = TestAssets.Get(testAppName) + .CreateInstance() + .WithSourceFiles(); + + var testProjectDirectory = testInstance.Root.FullName; + + new RestoreCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute("/p:SkipInvalidConfigurations=true") + .Should().Pass(); + + new BuildCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute() + .Should().Pass(); + + var cmd = new RunCommand() + .WithWorkingDirectory(testProjectDirectory) + .ExecuteWithCapturedOutput(); + + cmd.Should().Pass() + .And.HaveStdOutContaining("First"); + + cmd.StdErr.Should().BeEmpty(); + } + + [Fact] + public void ItGivesAnErrorWhenTheLaunchProfileNotFound() + { + var testAppName = "AppWithLaunchSettings"; + var testInstance = TestAssets.Get(testAppName) + .CreateInstance() + .WithSourceFiles(); + + var testProjectDirectory = testInstance.Root.FullName; + + new RestoreCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute("/p:SkipInvalidConfigurations=true") + .Should().Pass(); + + new BuildCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute() + .Should().Pass(); + + new RunCommand() + .WithWorkingDirectory(testProjectDirectory) + .ExecuteWithCapturedOutput("--launch-profile Third") + .Should().Pass() + .And.HaveStdOutContaining("(NO MESSAGE)") + .And.HaveStdErrContaining("The launch profile \"Third\" could not be applied."); + } + + [Fact] + public void ItGivesAnErrorWhenTheLaunchProfileCanNotBeHandled() + { + var testAppName = "AppWithLaunchSettings"; + var testInstance = TestAssets.Get(testAppName) + .CreateInstance() + .WithSourceFiles(); + + var testProjectDirectory = testInstance.Root.FullName; + + new RestoreCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute("/p:SkipInvalidConfigurations=true") + .Should().Pass(); + + new BuildCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute() + .Should().Pass(); + + new RunCommand() + .WithWorkingDirectory(testProjectDirectory) + .ExecuteWithCapturedOutput("--launch-profile \"IIS Express\"") + .Should().Pass() + .And.HaveStdOutContaining("(NO MESSAGE)") + .And.HaveStdErrContaining("The launch profile \"IIS Express\" could not be applied."); + } + + [Fact] + public void ItSkipsLaunchProfilesWhenTheSwitchIsSupplied() + { + var testAppName = "AppWithLaunchSettings"; + var testInstance = TestAssets.Get(testAppName) + .CreateInstance() + .WithSourceFiles(); + + var testProjectDirectory = testInstance.Root.FullName; + + new RestoreCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute("/p:SkipInvalidConfigurations=true") + .Should().Pass(); + + new BuildCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute() + .Should().Pass(); + + var cmd = new RunCommand() + .WithWorkingDirectory(testProjectDirectory) + .ExecuteWithCapturedOutput("--no-launch-profile"); + + cmd.Should().Pass() + .And.HaveStdOutContaining("(NO MESSAGE)"); + + cmd.StdErr.Should().BeEmpty(); + } + + [Fact] + public void ItSkipsLaunchProfilesWhenTheSwitchIsSuppliedWithoutErrorWhenThereAreNoLaunchSettings() + { + var testAppName = "MSBuildTestApp"; + var testInstance = TestAssets.Get(testAppName) + .CreateInstance() + .WithSourceFiles(); + + var testProjectDirectory = testInstance.Root.FullName; + + new RestoreCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute("/p:SkipInvalidConfigurations=true") + .Should().Pass(); + + new BuildCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute() + .Should().Pass(); + + var cmd = new RunCommand() + .WithWorkingDirectory(testProjectDirectory) + .ExecuteWithCapturedOutput("--no-launch-profile"); + + cmd.Should().Pass() + .And.HaveStdOutContaining("Hello World!"); + + cmd.StdErr.Should().BeEmpty(); + } + + [Fact] + public void ItSkipsLaunchProfilesWhenThereIsNoUsableDefault() + { + var testAppName = "AppWithLaunchSettingsNoDefault"; + var testInstance = TestAssets.Get(testAppName) + .CreateInstance() + .WithSourceFiles(); + + var testProjectDirectory = testInstance.Root.FullName; + + new RestoreCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute("/p:SkipInvalidConfigurations=true") + .Should().Pass(); + + new BuildCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute() + .Should().Pass(); + + var cmd = new RunCommand() + .WithWorkingDirectory(testProjectDirectory) + .ExecuteWithCapturedOutput(); + + cmd.Should().Pass() + .And.HaveStdOutContaining("(NO MESSAGE)") + .And.HaveStdErrContaining("The launch profile \"(Default)\" could not be applied."); + } + + [Fact] + public void ItPrintsAnErrorWhenLaunchSettingsAreCorrupted() + { + var testAppName = "AppWithCorruptedLaunchSettings"; + var testInstance = TestAssets.Get(testAppName) + .CreateInstance() + .WithSourceFiles(); + + var testProjectDirectory = testInstance.Root.FullName; + + new RestoreCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute("/p:SkipInvalidConfigurations=true") + .Should().Pass(); + + new BuildCommand() + .WithWorkingDirectory(testProjectDirectory) + .Execute() + .Should().Pass(); + + var cmd = new RunCommand() + .WithWorkingDirectory(testProjectDirectory) + .ExecuteWithCapturedOutput(); + + cmd.Should().Pass() + .And.HaveStdOutContaining("(NO MESSAGE)") + .And.HaveStdErrContaining("The launch profile \"(Default)\" could not be applied."); + } } }