diff --git a/TestAssets/NonRestoredTestProjects/AppThrowingException/App/AppThrowingException.csproj b/TestAssets/NonRestoredTestProjects/AppThrowingException/App/AppThrowingException.csproj new file mode 100644 index 000000000..366798fca --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/AppThrowingException/App/AppThrowingException.csproj @@ -0,0 +1,22 @@ + + + + Exe + netcoreapp1.0 + dotnet-throwingtool + $(AssemblyName) + + + + + $(ProjectRuntimeConfigFilePath) + + + + + + 1.0.3 + + + + diff --git a/TestAssets/NonRestoredTestProjects/AppThrowingException/App/Program.cs b/TestAssets/NonRestoredTestProjects/AppThrowingException/App/Program.cs new file mode 100644 index 000000000..5d9d13152 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/AppThrowingException/App/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace AppThrowing +{ + class MyException : Exception + { + static void Main(string[] args) + { + throw new MyException(); + } + } +} \ No newline at end of file diff --git a/TestAssets/NonRestoredTestProjects/AppThrowingException/AppDependingOnOtherAsTool/AppDependingOnOtherAsTool.csproj b/TestAssets/NonRestoredTestProjects/AppThrowingException/AppDependingOnOtherAsTool/AppDependingOnOtherAsTool.csproj new file mode 100644 index 000000000..04b137131 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/AppThrowingException/AppDependingOnOtherAsTool/AppDependingOnOtherAsTool.csproj @@ -0,0 +1,20 @@ + + + + Exe + netcoreapp1.0 + + + + + 1.0.3 + + + + + + 1.0.0 + + + + diff --git a/TestAssets/NonRestoredTestProjects/AppThrowingException/AppDependingOnOtherAsTool/NuGet.Config b/TestAssets/NonRestoredTestProjects/AppThrowingException/AppDependingOnOtherAsTool/NuGet.Config new file mode 100644 index 000000000..97af9d3d0 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/AppThrowingException/AppDependingOnOtherAsTool/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + diff --git a/TestAssets/NonRestoredTestProjects/AppThrowingException/AppDependingOnOtherAsTool/Program.cs b/TestAssets/NonRestoredTestProjects/AppThrowingException/AppDependingOnOtherAsTool/Program.cs new file mode 100644 index 000000000..846370cce --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/AppThrowingException/AppDependingOnOtherAsTool/Program.cs @@ -0,0 +1,8 @@ +using System; + +class Program +{ + static void Main(string[] args) + { + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/ExceptionExtensions.cs b/src/Microsoft.DotNet.Cli.Utils/ExceptionExtensions.cs new file mode 100644 index 000000000..68743b1d8 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/ExceptionExtensions.cs @@ -0,0 +1,15 @@ +// 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 Microsoft.DotNet.Cli.Utils.ExceptionExtensions +{ + internal static class ExceptionExtensions + { + public static void ReportAsWarning(this Exception e) + { + Reporter.Verbose.WriteLine($"Warning: Ignoring exception: {e.ToString().Yellow()}"); + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs b/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs index f64042329..2f2353cbe 100644 --- a/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs +++ b/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs @@ -88,6 +88,20 @@ namespace Microsoft.DotNet.Tools.Common } } + public static bool TryDeleteDirectory(string directoryPath) + { + try + { + Directory.Delete(directoryPath, true); + + return true; + } + catch + { + return false; + } + } + /// /// Returns childItem relative to directory, with Path.DirectorySeparatorChar as separator /// diff --git a/src/Microsoft.DotNet.Cli.Utils/Properties/AssemblyInfo.cs b/src/Microsoft.DotNet.Cli.Utils/Properties/AssemblyInfo.cs index 7d41c16db..e5ed6ddf4 100644 --- a/src/Microsoft.DotNet.Cli.Utils/Properties/AssemblyInfo.cs +++ b/src/Microsoft.DotNet.Cli.Utils/Properties/AssemblyInfo.cs @@ -6,3 +6,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.DotNet.Tools.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.DotNet.Cli.Utils.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.DotNet.Tools.Tests.Utilities, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectJsonMigration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs b/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs index 01f579b84..9a71ba5d2 100644 --- a/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs +++ b/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs @@ -36,15 +36,9 @@ namespace Microsoft.Extensions.EnvironmentAbstractions public void CreateEmptyFile(string path) { - try + using (File.Create(path)) { - var emptyFile = File.Create(path); - if (emptyFile != null) - { - emptyFile.Dispose(); - } } - catch { } } } } \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs index 98f7e4cf4..eee159c59 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs @@ -3,12 +3,13 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using Microsoft.Build.Construction; using Microsoft.DotNet.Internal.ProjectModel; using Microsoft.DotNet.Internal.ProjectModel.Graph; using Microsoft.DotNet.Cli; -using System.Linq; -using System.IO; +using Microsoft.DotNet.Cli.Utils.ExceptionExtensions; using Microsoft.DotNet.Cli.Sln.Internal; using Microsoft.DotNet.ProjectJsonMigration.Rules; using Microsoft.DotNet.Tools.Common; @@ -87,14 +88,22 @@ namespace Microsoft.DotNet.ProjectJsonMigration try { File.Delete(Path.Combine(rootsettings.ProjectDirectory, "project.json")); - } catch {} + } + catch (Exception e) + { + e.ReportAsWarning(); + } foreach (var projectDependency in projectDependencies) { try { File.Delete(projectDependency.ProjectFilePath); - } catch { } + } + catch (Exception e) + { + e.ReportAsWarning(); + } } } diff --git a/src/dotnet-archive/Program.cs b/src/dotnet-archive/Program.cs index 0c8a1be39..3376bf47e 100644 --- a/src/dotnet-archive/Program.cs +++ b/src/dotnet-archive/Program.cs @@ -90,21 +90,7 @@ namespace Microsoft.DotNet.Tools.Archive return 0; }); - try - { - return app.Execute(args); - } - catch (Exception ex) - { -#if DEBUG - //Reporter.Error.WriteLine(ex.ToString()); - Console.WriteLine(ex.ToString()); -#else - // Reporter.Error.WriteLine(ex.Message); - Console.WriteLine(ex.Message); -#endif - return 1; - } + return app.Execute(args); } } } diff --git a/src/dotnet/MulticoreJitActivator.cs b/src/dotnet/MulticoreJitActivator.cs index e51a4e5dc..a22bb39ab 100644 --- a/src/dotnet/MulticoreJitActivator.cs +++ b/src/dotnet/MulticoreJitActivator.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.Loader; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Cli.Utils.ExceptionExtensions; using Microsoft.DotNet.Tools.Common; namespace Microsoft.DotNet.Cli @@ -51,8 +52,9 @@ namespace Microsoft.DotNet.Cli return true; } - catch (Exception) + catch (Exception e) { + e.ReportAsWarning(); return false; } } diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index a28b0bc37..faefe0a31 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -82,22 +82,6 @@ namespace Microsoft.DotNet.Cli return 1; } - catch (Exception ex) - { -#if DEBUG - Reporter.Error.WriteLine(ex.ToString()); -#else - if (Reporter.IsVerbose) - { - Reporter.Error.WriteLine(ex.ToString()); - } - else - { - Reporter.Error.WriteLine(ex.Message); - } -#endif - return 1; - } finally { if (PerfTrace.Enabled) diff --git a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs index a814acd82..9a61d3a3e 100644 --- a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs +++ b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs @@ -309,7 +309,7 @@ namespace Microsoft.DotNet.Tools.Migrate if (!projects.Any()) { - throw new Exception("Unable to find any projects in global.json"); + throw new GracefulException("Unable to find any projects in global.json"); } } else if (File.Exists(projectArg) && @@ -319,7 +319,7 @@ namespace Microsoft.DotNet.Tools.Migrate if (!projects.Any()) { - throw new Exception($"Unable to find any projects in {projectArg}"); + throw new GracefulException($"Unable to find any projects in {projectArg}"); } } else if (Directory.Exists(projectArg)) @@ -328,12 +328,12 @@ namespace Microsoft.DotNet.Tools.Migrate if (!projects.Any()) { - throw new Exception($"No project.json file found in '{projectArg}'"); + throw new GracefulException($"No project.json file found in '{projectArg}'"); } } else { - throw new Exception($"Invalid project argument - '{projectArg}' is not a project.json, global.json, or solution.sln file and a directory named '{projectArg}' doesn't exist."); + throw new GracefulException($"Invalid project argument - '{projectArg}' is not a project.json, global.json, or solution.sln file and a directory named '{projectArg}' doesn't exist."); } foreach (var project in projects) @@ -346,7 +346,7 @@ namespace Microsoft.DotNet.Tools.Migrate { if (variable == null) { - throw new Exception(message); + throw new GracefulException(message); } } @@ -359,14 +359,14 @@ namespace Microsoft.DotNet.Tools.Migrate return projectJson; } - throw new Exception($"Unable to find project file at {projectJson}"); + throw new GracefulException($"Unable to find project file at {projectJson}"); } private IEnumerable GetProjectsFromGlobalJson(string globalJson) { if (!File.Exists(globalJson)) { - throw new Exception($"Unable to find global settings file at {globalJson}"); + throw new GracefulException($"Unable to find global settings file at {globalJson}"); } var searchPaths = ProjectDependencyFinder.GetGlobalPaths(Path.GetDirectoryName(globalJson)); @@ -396,7 +396,7 @@ namespace Microsoft.DotNet.Tools.Migrate { if (!File.Exists(slnPath)) { - throw new Exception($"Unable to find the solution file at {slnPath}"); + throw new GracefulException($"Unable to find the solution file at {slnPath}"); } _slnFile = SlnFile.Read(slnPath); diff --git a/src/dotnet/commands/dotnet-migrate/Program.cs b/src/dotnet/commands/dotnet-migrate/Program.cs index 86104dd0e..469b29b50 100644 --- a/src/dotnet/commands/dotnet-migrate/Program.cs +++ b/src/dotnet/commands/dotnet-migrate/Program.cs @@ -78,16 +78,17 @@ namespace Microsoft.DotNet.Tools.Migrate { return app.Execute(args); } - catch (Exception ex) + catch (GracefulException e) { -#if DEBUG - Reporter.Error.WriteLine(ex.ToString()); -#else - Reporter.Error.WriteLine(ex.Message); -#endif + Reporter.Error.WriteLine(e.Message); Reporter.Error.WriteLine(LocalizableStrings.MigrationFailedError); return 1; } + catch (Exception e) + { + Reporter.Error.WriteLine(LocalizableStrings.MigrationFailedError); + throw e; + } } } } diff --git a/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs b/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs index 3c4b2d39a..12fcbda25 100644 --- a/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs +++ b/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs @@ -3,6 +3,7 @@ using Microsoft.DotNet.Cli; using System; using System.Collections.Generic; using System.IO; +using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.ProjectJsonMigration; using Microsoft.Build.Evaluation; @@ -77,7 +78,7 @@ namespace Microsoft.DotNet.Tools.Migrate MigrationTrace.Instance.WriteLine(commandResult.StdOut); MigrationTrace.Instance.WriteLine(commandResult.StdErr); - throw new Exception($"Failed to run {commandToExecute} in directory: {workingDirectory}"); + throw new GracefulException($"Failed to run {commandToExecute} in directory: {workingDirectory}"); } } } diff --git a/src/dotnet/commands/dotnet-new/Program.cs b/src/dotnet/commands/dotnet-new/Program.cs index 0824ae1ed..f221f011d 100644 --- a/src/dotnet/commands/dotnet-new/Program.cs +++ b/src/dotnet/commands/dotnet-new/Program.cs @@ -155,19 +155,7 @@ namespace Microsoft.DotNet.Tools.New return dotnetNew.CreateEmptyProject(language.Name, fullTemplateName); }); - try - { - return app.Execute(args); - } - catch (Exception ex) - { -#if DEBUG - Reporter.Error.WriteLine(ex.ToString()); -#else - Reporter.Error.WriteLine(ex.Message); -#endif - return 1; - } + return app.Execute(args); } } } diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAppThrowingException.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAppThrowingException.cs new file mode 100644 index 000000000..fb4299442 --- /dev/null +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAppThrowingException.cs @@ -0,0 +1,78 @@ +// 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.IO; +using FluentAssertions; +using Microsoft.DotNet.Tools.Test.Utilities; +using Xunit; + +namespace Microsoft.DotNet.Cli.Utils.Tests +{ + public class GivenAppThrowingException : TestBase + { + [Fact] + public void ItShowsStackTraceWhenRun() + { + var root = TestAssets.Get("NonRestoredTestProjects", "AppThrowingException") + .CreateInstance() + .WithSourceFiles() + .Root; + + var appRoot = Path.Combine(root.FullName, "App"); + + new RestoreCommand() + .WithWorkingDirectory(appRoot) + .Execute() + .Should().Pass(); + + string msg1 = "Unhandled Exception: AppThrowing.MyException: " + + "Exception of type 'AppThrowing.MyException' was thrown."; + string msg2 = "at AppThrowing.MyException.Main(String[] args)"; + new RunCommand() + .WithWorkingDirectory(appRoot) + .ExecuteWithCapturedOutput() + .Should().Fail() + .And.HaveStdErrContaining(msg1) + .And.HaveStdErrContaining(msg2); + } + + [Fact] + public void ItShowsStackTraceWhenRunAsTool() + { + var root = TestAssets.Get("NonRestoredTestProjects", "AppThrowingException") + .CreateInstance() + .WithSourceFiles() + .Root; + + var appRoot = Path.Combine(root.FullName, "App"); + + new RestoreCommand() + .WithWorkingDirectory(appRoot) + .Execute() + .Should().Pass(); + + new PackCommand() + .WithWorkingDirectory(appRoot) + .Execute("-o ../pkgs") + .Should() + .Pass(); + + var appWithToolDepRoot = Path.Combine(root.FullName, "AppDependingOnOtherAsTool"); + + new RestoreCommand() + .WithWorkingDirectory(appWithToolDepRoot) + .Execute() + .Should().Pass(); + + string msg1 = "Unhandled Exception: AppThrowing.MyException: " + + "Exception of type 'AppThrowing.MyException' was thrown."; + string msg2 = "at AppThrowing.MyException.Main(String[] args)"; + new TestCommand("dotnet") + .WithWorkingDirectory(appWithToolDepRoot) + .ExecuteWithCapturedOutput("throwingtool") + .Should().Fail() + .And.HaveStdErrContaining(msg1) + .And.HaveStdErrContaining(msg2); + } + } +}