From 080b72f73f1ea7f87e71ea761fc91f36dc40cdf1 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 8 Feb 2021 11:12:32 -0800 Subject: [PATCH] Remove dotnet-watch (#29997) --- AspNetCore.sln | 63 --- eng/ProjectReferences.props | 1 - src/Tools/Tools.slnf | 4 - .../src/BrowserRefreshMiddleware.cs | 107 ----- .../src/BrowserScriptMiddleware.cs | 51 --- .../BrowserRefresh/src/HostingFilter.cs | 32 -- ...oft.AspNetCore.Watch.BrowserRefresh.csproj | 18 - .../src/ResponseStreamWrapper.cs | 153 ------- .../BrowserRefresh/src/StartupHook.cs | 10 - .../src/WebSocketScriptInjection.cs | 74 ---- .../src/WebSocketScriptInjection.js | 63 --- .../test/BrowserRefreshMiddlewareTest.cs | 106 ----- .../test/BrowserScriptMiddlewareTest.cs | 66 --- ...pNetCore.Watch.BrowserRefresh.Tests.csproj | 35 -- .../test/ResponseStreamWrapperTest.cs | 316 ------------- .../test/WebSockerScriptInjectionTest.cs | 192 -------- .../BrowserRefresh/test/wwwroot/favicon.ico | Bin 5430 -> 0 bytes .../BrowserRefresh/test/wwwroot/index.html | 11 - .../test/wwwroot/largefile.html | 62 --- src/Tools/dotnet-watch/README.md | 92 ---- .../dotnet-watch/src/BrowserRefreshServer.cs | 116 ----- .../dotnet-watch/src/CommandLineOptions.cs | 121 ----- .../dotnet-watch/src/DotNetWatchContext.cs | 26 -- .../dotnet-watch/src/DotNetWatchOptions.cs | 24 - src/Tools/dotnet-watch/src/DotNetWatcher.cs | 182 -------- .../dotnet-watch/src/DotnetToolSettings.xml | 6 - src/Tools/dotnet-watch/src/FileItem.cs | 21 - src/Tools/dotnet-watch/src/FileKind.cs | 11 - src/Tools/dotnet-watch/src/FileSet.cs | 36 -- src/Tools/dotnet-watch/src/IFileSetFactory.cs | 14 - src/Tools/dotnet-watch/src/IWatchFilter.cs | 13 - .../src/Internal/FileSetWatcher.cs | 61 --- .../dotnet-watch/src/Internal/FileWatcher.cs | 143 ------ .../Internal/FileWatcher/DotnetFileWatcher.cs | 159 ------- .../FileWatcher/FileWatcherFactory.cs | 20 - .../FileWatcher/IFileSystemWatcher.cs | 18 - .../FileWatcher/PollingFileWatcher.cs | 257 ----------- .../src/Internal/MsBuildFileSetFactory.cs | 217 --------- .../src/Internal/MsBuildProjectFinder.cs | 57 --- .../src/Internal/OutputCapture.cs | 14 - .../dotnet-watch/src/Internal/OutputSink.cs | 14 - .../src/Internal/ProcessRunner.cs | 191 -------- .../dotnet-watch/src/LaunchBrowserFilter.cs | 230 ---------- .../dotnet-watch/src/LaunchSettingsJson.cs | 21 - .../src/MSBuildEvaluationFilter.cs | 124 ------ src/Tools/dotnet-watch/src/NoRestoreFilter.cs | 75 ---- .../dotnet-watch/src/PrefixConsoleReporter.cs | 34 -- src/Tools/dotnet-watch/src/ProcessSpec.cs | 29 -- src/Tools/dotnet-watch/src/Program.cs | 242 ---------- .../src/Properties/AssemblyInfo.cs | 6 - src/Tools/dotnet-watch/src/Resources.resx | 132 ------ .../dotnet-watch/src/StaticContentHandler.cs | 37 -- .../src/assets/DotNetWatch.targets | 94 ---- .../dotnet-watch/src/dotnet-watch.csproj | 56 --- .../src/runtimeconfig.template.json | 3 - .../dotnet-watch/test/AppWithDepsTests.cs | 56 --- src/Tools/dotnet-watch/test/AssertEx.cs | 67 --- .../dotnet-watch/test/AwaitableProcess.cs | 194 -------- .../dotnet-watch/test/BrowserLaunchTests.cs | 73 --- .../test/CommandLineOptionsTests.cs | 59 --- .../dotnet-watch/test/ConsoleReporterTests.cs | 83 ---- .../dotnet-watch/test/DotNetWatcherTests.cs | 122 ----- .../dotnet-watch/test/FileWatcherTests.cs | 419 ------------------ .../dotnet-watch/test/GlobbingAppTests.cs | 153 ------- .../test/MSBuildEvaluationFilterTest.cs | 139 ------ .../test/MsBuildFileSetFactoryTest.cs | 412 ----------------- src/Tools/dotnet-watch/test/NoDepsAppTests.cs | 78 ---- .../dotnet-watch/test/NoRestoreFilterTest.cs | 193 -------- src/Tools/dotnet-watch/test/ProgramTests.cs | 56 --- .../test/Properties/AssemblyInfo.cs | 7 - .../test/Scenario/ProjectToolScenario.cs | 181 -------- .../test/Scenario/WatchableApp.cs | 161 ------- .../test/StaticContentHandlerTest.cs | 57 --- .../AppWithDeps/AppWithDeps.csproj | 13 - .../test/TestProjects/AppWithDeps/Program.cs | 22 - .../AppWithLaunchSettings.csproj | 9 - .../AppWithLaunchSettings/Program.cs | 32 -- .../Properties/launchSettings.json | 29 -- .../TestProjects/Dependency/Dependency.csproj | 8 - .../test/TestProjects/Dependency/Foo.cs | 9 - .../GlobbingApp/GlobbingApp.csproj | 14 - .../test/TestProjects/GlobbingApp/Program.cs | 23 - .../TestProjects/GlobbingApp/exclude/Baz.cs | 10 - .../TestProjects/GlobbingApp/include/Foo.cs | 9 - .../test/TestProjects/KitchenSink/.gitignore | 1 - .../KitchenSink/KitchenSink.csproj | 18 - .../test/TestProjects/KitchenSink/Program.cs | 27 -- .../TestProjects/NoDepsApp/NoDepsApp.csproj | 9 - .../test/TestProjects/NoDepsApp/Program.cs | 24 - .../test/Utilities/TestProjectGraph.cs | 41 -- .../test/dotnet-watch.Tests.csproj | 69 --- 91 files changed, 7137 deletions(-) delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/src/BrowserRefreshMiddleware.cs delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/src/BrowserScriptMiddleware.cs delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/src/HostingFilter.cs delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/src/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/src/ResponseStreamWrapper.cs delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/src/StartupHook.cs delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/src/WebSocketScriptInjection.cs delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/src/WebSocketScriptInjection.js delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/test/BrowserRefreshMiddlewareTest.cs delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/test/BrowserScriptMiddlewareTest.cs delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/test/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests.csproj delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/test/ResponseStreamWrapperTest.cs delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/test/WebSockerScriptInjectionTest.cs delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/test/wwwroot/favicon.ico delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/test/wwwroot/index.html delete mode 100644 src/Tools/dotnet-watch/BrowserRefresh/test/wwwroot/largefile.html delete mode 100644 src/Tools/dotnet-watch/README.md delete mode 100644 src/Tools/dotnet-watch/src/BrowserRefreshServer.cs delete mode 100644 src/Tools/dotnet-watch/src/CommandLineOptions.cs delete mode 100644 src/Tools/dotnet-watch/src/DotNetWatchContext.cs delete mode 100644 src/Tools/dotnet-watch/src/DotNetWatchOptions.cs delete mode 100644 src/Tools/dotnet-watch/src/DotNetWatcher.cs delete mode 100644 src/Tools/dotnet-watch/src/DotnetToolSettings.xml delete mode 100644 src/Tools/dotnet-watch/src/FileItem.cs delete mode 100644 src/Tools/dotnet-watch/src/FileKind.cs delete mode 100644 src/Tools/dotnet-watch/src/FileSet.cs delete mode 100644 src/Tools/dotnet-watch/src/IFileSetFactory.cs delete mode 100644 src/Tools/dotnet-watch/src/IWatchFilter.cs delete mode 100644 src/Tools/dotnet-watch/src/Internal/FileSetWatcher.cs delete mode 100644 src/Tools/dotnet-watch/src/Internal/FileWatcher.cs delete mode 100644 src/Tools/dotnet-watch/src/Internal/FileWatcher/DotnetFileWatcher.cs delete mode 100644 src/Tools/dotnet-watch/src/Internal/FileWatcher/FileWatcherFactory.cs delete mode 100644 src/Tools/dotnet-watch/src/Internal/FileWatcher/IFileSystemWatcher.cs delete mode 100644 src/Tools/dotnet-watch/src/Internal/FileWatcher/PollingFileWatcher.cs delete mode 100644 src/Tools/dotnet-watch/src/Internal/MsBuildFileSetFactory.cs delete mode 100644 src/Tools/dotnet-watch/src/Internal/MsBuildProjectFinder.cs delete mode 100644 src/Tools/dotnet-watch/src/Internal/OutputCapture.cs delete mode 100644 src/Tools/dotnet-watch/src/Internal/OutputSink.cs delete mode 100644 src/Tools/dotnet-watch/src/Internal/ProcessRunner.cs delete mode 100644 src/Tools/dotnet-watch/src/LaunchBrowserFilter.cs delete mode 100644 src/Tools/dotnet-watch/src/LaunchSettingsJson.cs delete mode 100644 src/Tools/dotnet-watch/src/MSBuildEvaluationFilter.cs delete mode 100644 src/Tools/dotnet-watch/src/NoRestoreFilter.cs delete mode 100644 src/Tools/dotnet-watch/src/PrefixConsoleReporter.cs delete mode 100644 src/Tools/dotnet-watch/src/ProcessSpec.cs delete mode 100644 src/Tools/dotnet-watch/src/Program.cs delete mode 100644 src/Tools/dotnet-watch/src/Properties/AssemblyInfo.cs delete mode 100644 src/Tools/dotnet-watch/src/Resources.resx delete mode 100644 src/Tools/dotnet-watch/src/StaticContentHandler.cs delete mode 100644 src/Tools/dotnet-watch/src/assets/DotNetWatch.targets delete mode 100644 src/Tools/dotnet-watch/src/dotnet-watch.csproj delete mode 100644 src/Tools/dotnet-watch/src/runtimeconfig.template.json delete mode 100644 src/Tools/dotnet-watch/test/AppWithDepsTests.cs delete mode 100644 src/Tools/dotnet-watch/test/AssertEx.cs delete mode 100644 src/Tools/dotnet-watch/test/AwaitableProcess.cs delete mode 100644 src/Tools/dotnet-watch/test/BrowserLaunchTests.cs delete mode 100644 src/Tools/dotnet-watch/test/CommandLineOptionsTests.cs delete mode 100644 src/Tools/dotnet-watch/test/ConsoleReporterTests.cs delete mode 100644 src/Tools/dotnet-watch/test/DotNetWatcherTests.cs delete mode 100644 src/Tools/dotnet-watch/test/FileWatcherTests.cs delete mode 100644 src/Tools/dotnet-watch/test/GlobbingAppTests.cs delete mode 100644 src/Tools/dotnet-watch/test/MSBuildEvaluationFilterTest.cs delete mode 100644 src/Tools/dotnet-watch/test/MsBuildFileSetFactoryTest.cs delete mode 100644 src/Tools/dotnet-watch/test/NoDepsAppTests.cs delete mode 100644 src/Tools/dotnet-watch/test/NoRestoreFilterTest.cs delete mode 100644 src/Tools/dotnet-watch/test/ProgramTests.cs delete mode 100644 src/Tools/dotnet-watch/test/Properties/AssemblyInfo.cs delete mode 100644 src/Tools/dotnet-watch/test/Scenario/ProjectToolScenario.cs delete mode 100644 src/Tools/dotnet-watch/test/Scenario/WatchableApp.cs delete mode 100644 src/Tools/dotnet-watch/test/StaticContentHandlerTest.cs delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/Program.cs delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/AppWithLaunchSettings.csproj delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/Program.cs delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/Properties/launchSettings.json delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/Dependency/Dependency.csproj delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/Dependency/Foo.cs delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/Program.cs delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/exclude/Baz.cs delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/include/Foo.cs delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/KitchenSink/.gitignore delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/KitchenSink/Program.cs delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj delete mode 100644 src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/Program.cs delete mode 100644 src/Tools/dotnet-watch/test/Utilities/TestProjectGraph.cs delete mode 100644 src/Tools/dotnet-watch/test/dotnet-watch.Tests.csproj diff --git a/AspNetCore.sln b/AspNetCore.sln index 03ad965f2bf1..19a61c8895b4 100644 --- a/AspNetCore.sln +++ b/AspNetCore.sln @@ -1232,12 +1232,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.FilePr EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{0B200A66-B809-4ED3-A790-CB1C2E80975E}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet-watch", "dotnet-watch", "{B6118E15-C37A-4B05-B4DF-97FE99790417}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch", "src\Tools\dotnet-watch\src\dotnet-watch.csproj", "{D0ADA8EC-F431-43C8-A86E-FE6A1E906512}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch.Tests", "src\Tools\dotnet-watch\test\dotnet-watch.Tests.csproj", "{95920BAA-46E6-44E6-A1AF-A23804F079D2}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet-dev-certs", "dotnet-dev-certs", "{A4EECF29-6E66-4E7F-B781-A169B0C2AB29}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-dev-certs", "src\Tools\dotnet-dev-certs\src\dotnet-dev-certs.csproj", "{52433D20-35EA-48CC-BB4A-4DFE3023670B}" @@ -1438,10 +1432,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RequestHandlerLib", "src\Se EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.ANCMSymbols", "src\Servers\IIS\AspNetCoreModuleV2\Symbols\Microsoft.AspNetCore.ANCMSymbols.csproj", "{7E268085-1046-4362-80CB-2977FF826DCA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Watch.BrowserRefresh", "src\Tools\dotnet-watch\BrowserRefresh\src\Microsoft.AspNetCore.Watch.BrowserRefresh.csproj", "{A5CE25E9-89E1-4F2C-9B89-0C161707E700}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Watch.BrowserRefresh.Tests", "src\Tools\dotnet-watch\BrowserRefresh\test\Microsoft.AspNetCore.Watch.BrowserRefresh.Tests.csproj", "{E6A23627-8D63-4DF1-A4F2-8881172C1FE6}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{7D2B0799-A634-42AC-AE77-5D167BA51389}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostedInAspNet.Client", "src\Components\WebAssembly\testassets\HostedInAspNet.Client\HostedInAspNet.Client.csproj", "{9788C76F-658B-4441-88F8-22C6B86FAD27}" @@ -5992,30 +5982,6 @@ Global {65EE0531-4533-407F-A9CA-2EBCDC444397}.Release|x64.Build.0 = Release|Any CPU {65EE0531-4533-407F-A9CA-2EBCDC444397}.Release|x86.ActiveCfg = Release|Any CPU {65EE0531-4533-407F-A9CA-2EBCDC444397}.Release|x86.Build.0 = Release|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Debug|x64.ActiveCfg = Debug|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Debug|x64.Build.0 = Debug|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Debug|x86.ActiveCfg = Debug|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Debug|x86.Build.0 = Debug|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Release|Any CPU.Build.0 = Release|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Release|x64.ActiveCfg = Release|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Release|x64.Build.0 = Release|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Release|x86.ActiveCfg = Release|Any CPU - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512}.Release|x86.Build.0 = Release|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Debug|x64.ActiveCfg = Debug|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Debug|x64.Build.0 = Debug|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Debug|x86.ActiveCfg = Debug|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Debug|x86.Build.0 = Debug|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Release|Any CPU.Build.0 = Release|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Release|x64.ActiveCfg = Release|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Release|x64.Build.0 = Release|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Release|x86.ActiveCfg = Release|Any CPU - {95920BAA-46E6-44E6-A1AF-A23804F079D2}.Release|x86.Build.0 = Release|Any CPU {52433D20-35EA-48CC-BB4A-4DFE3023670B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {52433D20-35EA-48CC-BB4A-4DFE3023670B}.Debug|Any CPU.Build.0 = Debug|Any CPU {52433D20-35EA-48CC-BB4A-4DFE3023670B}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -6893,30 +6859,6 @@ Global {7E268085-1046-4362-80CB-2977FF826DCA}.Release|x64.Build.0 = Release|Any CPU {7E268085-1046-4362-80CB-2977FF826DCA}.Release|x86.ActiveCfg = Release|Any CPU {7E268085-1046-4362-80CB-2977FF826DCA}.Release|x86.Build.0 = Release|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Debug|x64.ActiveCfg = Debug|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Debug|x64.Build.0 = Debug|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Debug|x86.ActiveCfg = Debug|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Debug|x86.Build.0 = Debug|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Release|Any CPU.Build.0 = Release|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Release|x64.ActiveCfg = Release|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Release|x64.Build.0 = Release|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Release|x86.ActiveCfg = Release|Any CPU - {A5CE25E9-89E1-4F2C-9B89-0C161707E700}.Release|x86.Build.0 = Release|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Debug|x64.ActiveCfg = Debug|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Debug|x64.Build.0 = Debug|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Debug|x86.ActiveCfg = Debug|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Debug|x86.Build.0 = Debug|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Release|Any CPU.Build.0 = Release|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Release|x64.ActiveCfg = Release|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Release|x64.Build.0 = Release|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Release|x86.ActiveCfg = Release|Any CPU - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6}.Release|x86.Build.0 = Release|Any CPU {9788C76F-658B-4441-88F8-22C6B86FAD27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9788C76F-658B-4441-88F8-22C6B86FAD27}.Debug|Any CPU.Build.0 = Debug|Any CPU {9788C76F-658B-4441-88F8-22C6B86FAD27}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -8099,9 +8041,6 @@ Global {898F7E0B-1671-42CB-9DFB-689AFF212ED3} = {FED63417-432B-49CD-AB4B-44ADA837C2E7} {65EE0531-4533-407F-A9CA-2EBCDC444397} = {898F7E0B-1671-42CB-9DFB-689AFF212ED3} {0B200A66-B809-4ED3-A790-CB1C2E80975E} = {017429CC-C5FB-48B4-9C46-034E29EE2F06} - {B6118E15-C37A-4B05-B4DF-97FE99790417} = {0B200A66-B809-4ED3-A790-CB1C2E80975E} - {D0ADA8EC-F431-43C8-A86E-FE6A1E906512} = {B6118E15-C37A-4B05-B4DF-97FE99790417} - {95920BAA-46E6-44E6-A1AF-A23804F079D2} = {B6118E15-C37A-4B05-B4DF-97FE99790417} {A4EECF29-6E66-4E7F-B781-A169B0C2AB29} = {0B200A66-B809-4ED3-A790-CB1C2E80975E} {52433D20-35EA-48CC-BB4A-4DFE3023670B} = {A4EECF29-6E66-4E7F-B781-A169B0C2AB29} {8562A154-B802-411B-897C-89621C4B05CB} = {0B200A66-B809-4ED3-A790-CB1C2E80975E} @@ -8202,8 +8141,6 @@ Global {7F87406C-A3C8-4139-A68D-E4C344294A67} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} {1533E271-F61B-441B-8B74-59FB61DF0552} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} {7E268085-1046-4362-80CB-2977FF826DCA} = {D62AF49B-F9FE-4794-AC39-A473FF13CA81} - {A5CE25E9-89E1-4F2C-9B89-0C161707E700} = {B6118E15-C37A-4B05-B4DF-97FE99790417} - {E6A23627-8D63-4DF1-A4F2-8881172C1FE6} = {B6118E15-C37A-4B05-B4DF-97FE99790417} {7D2B0799-A634-42AC-AE77-5D167BA51389} = {562D5067-8CD8-4F19-BCBB-873204932C61} {9788C76F-658B-4441-88F8-22C6B86FAD27} = {7D2B0799-A634-42AC-AE77-5D167BA51389} {1970D5CD-D9A4-4673-A297-179BB04199F4} = {7D2B0799-A634-42AC-AE77-5D167BA51389} diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index c3889fa6050a..3afacb916146 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -69,7 +69,6 @@ - diff --git a/src/Tools/Tools.slnf b/src/Tools/Tools.slnf index 2ad3d135e415..36f3b9f8bf2e 100644 --- a/src/Tools/Tools.slnf +++ b/src/Tools/Tools.slnf @@ -16,10 +16,6 @@ "src\\Tools\\dotnet-sql-cache\\src\\dotnet-sql-cache.csproj", "src\\Tools\\dotnet-user-secrets\\src\\dotnet-user-secrets.csproj", "src\\Tools\\dotnet-user-secrets\\test\\dotnet-user-secrets.Tests.csproj", - "src\\Tools\\dotnet-watch\\src\\dotnet-watch.csproj", - "src\\Tools\\dotnet-watch\\BrowserRefresh\\src\\Microsoft.AspNetCore.Watch.BrowserRefresh.csproj", - "src\\Tools\\dotnet-watch\\BrowserRefresh\\test\\Microsoft.AspNetCore.Watch.BrowserRefresh.Tests.csproj", - "src\\Tools\\dotnet-watch\\test\\dotnet-watch.Tests.csproj", "src\\Middleware\\StaticFiles\\src\\Microsoft.AspNetCore.StaticFiles.csproj", "src\\Hosting\\TestHost\\src\\Microsoft.AspNetCore.TestHost.csproj" ] diff --git a/src/Tools/dotnet-watch/BrowserRefresh/src/BrowserRefreshMiddleware.cs b/src/Tools/dotnet-watch/BrowserRefresh/src/BrowserRefreshMiddleware.cs deleted file mode 100644 index 210b4f73e890..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/src/BrowserRefreshMiddleware.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; - -namespace Microsoft.AspNetCore.Watch.BrowserRefresh -{ - public class BrowserRefreshMiddleware - { - private static readonly MediaTypeHeaderValue _textHtmlMediaType = new MediaTypeHeaderValue("text/html"); - private readonly RequestDelegate _next; - private readonly ILogger _logger; - - public BrowserRefreshMiddleware(RequestDelegate next, ILogger logger) => - (_next, _logger) = (next, logger); - - public async Task InvokeAsync(HttpContext context) - { - // We only need to support this for requests that could be initiated by a browser. - if (IsBrowserRequest(context)) - { - // Use a custom StreamWrapper to rewrite output on Write/WriteAsync - using var responseStreamWrapper = new ResponseStreamWrapper(context, _logger); - var originalBodyFeature = context.Features.Get(); - context.Features.Set(new StreamResponseBodyFeature(responseStreamWrapper)); - - try - { - await _next(context); - } - finally - { - context.Features.Set(originalBodyFeature); - } - - if (responseStreamWrapper.IsHtmlResponse && _logger.IsEnabled(LogLevel.Debug)) - { - if (responseStreamWrapper.ScriptInjectionPerformed) - { - Log.BrowserConfiguredForRefreshes(_logger); - } - else - { - Log.FailedToConfiguredForRefreshes(_logger); - } - } - } - else - { - await _next(context); - } - } - - internal static bool IsBrowserRequest(HttpContext context) - { - var request = context.Request; - if (!HttpMethods.IsGet(request.Method) && !HttpMethods.IsPost(request.Method)) - { - return false; - } - - var typedHeaders = request.GetTypedHeaders(); - if (!(typedHeaders.Accept is IList acceptHeaders)) - { - return false; - } - - for (var i = 0; i < acceptHeaders.Count; i++) - { - if (acceptHeaders[i].IsSubsetOf(_textHtmlMediaType)) - { - return true; - } - } - - return false; - } - - internal static class Log - { - private static readonly Action _setupResponseForBrowserRefresh = LoggerMessage.Define( - LogLevel.Debug, - new EventId(1, "SetUpResponseForBrowserRefresh"), - "Response markup is scheduled to include browser refresh script injection."); - - private static readonly Action _browserConfiguredForRefreshes = LoggerMessage.Define( - LogLevel.Debug, - new EventId(2, "BrowserConfiguredForRefreshes"), - "Response markup was updated to include browser refresh script injection."); - - private static readonly Action _failedToConfigureForRefreshes = LoggerMessage.Define( - LogLevel.Debug, - new EventId(3, "FailedToConfiguredForRefreshes"), - "Unable to configure browser refresh script injection on the response."); - - public static void SetupResponseForBrowserRefresh(ILogger logger) => _setupResponseForBrowserRefresh(logger, null); - public static void BrowserConfiguredForRefreshes(ILogger logger) => _browserConfiguredForRefreshes(logger, null); - public static void FailedToConfiguredForRefreshes(ILogger logger) => _failedToConfigureForRefreshes(logger, null); - } - } -} diff --git a/src/Tools/dotnet-watch/BrowserRefresh/src/BrowserScriptMiddleware.cs b/src/Tools/dotnet-watch/BrowserRefresh/src/BrowserScriptMiddleware.cs deleted file mode 100644 index b90ae7591ddc..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/src/BrowserScriptMiddleware.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Watch.BrowserRefresh -{ - /// - /// Responds with the contennts of WebSocketScriptInjection.js with the stub WebSocket url replaced by the - /// one specified by the launching app. - /// - public sealed class BrowserScriptMiddleware - { - private readonly byte[] _scriptBytes; - private readonly string _contentLength; - - public BrowserScriptMiddleware(RequestDelegate next) - : this(Environment.GetEnvironmentVariable("ASPNETCORE_AUTO_RELOAD_WS_ENDPOINT")!) - { - } - - internal BrowserScriptMiddleware(string webSocketUrl) - { - _scriptBytes = GetWebSocketClientJavaScript(webSocketUrl); - _contentLength = _scriptBytes.Length.ToString(CultureInfo.InvariantCulture); - } - - public async Task InvokeAsync(HttpContext context) - { - context.Response.Headers["Cache-Control"] = "no-store"; - context.Response.Headers["Content-Length"] = _contentLength; - context.Response.Headers["Content-Type"] = "application/javascript; charset=utf-8"; - - await context.Response.Body.WriteAsync(_scriptBytes.AsMemory(), context.RequestAborted); - } - - internal static byte[] GetWebSocketClientJavaScript(string hostString) - { - var jsFileName = "Microsoft.AspNetCore.Watch.BrowserRefresh.WebSocketScriptInjection.js"; - using var reader = new StreamReader(typeof(WebSocketScriptInjection).Assembly.GetManifestResourceStream(jsFileName)!); - var script = reader.ReadToEnd().Replace("{{hostString}}", hostString); - - return Encoding.UTF8.GetBytes(script); - } - } -} diff --git a/src/Tools/dotnet-watch/BrowserRefresh/src/HostingFilter.cs b/src/Tools/dotnet-watch/BrowserRefresh/src/HostingFilter.cs deleted file mode 100644 index 68f32444b707..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/src/HostingFilter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -[assembly: HostingStartup(typeof(Microsoft.AspNetCore.Watch.BrowserRefresh.HostingStartup))] - -namespace Microsoft.AspNetCore.Watch.BrowserRefresh -{ - internal sealed class HostingStartup : IHostingStartup, IStartupFilter - { - public void Configure(IWebHostBuilder builder) - { - builder.ConfigureServices(services => services.TryAddEnumerable(ServiceDescriptor.Singleton(this))); - } - - public Action Configure(Action next) - { - return app => - { - app.Map(WebSocketScriptInjection.WebSocketScriptUrl, app1 => app1.UseMiddleware()); - app.UseMiddleware(); - next(app); - }; - } - } -} diff --git a/src/Tools/dotnet-watch/BrowserRefresh/src/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj b/src/Tools/dotnet-watch/BrowserRefresh/src/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj deleted file mode 100644 index 017eb2028c9d..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/src/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netcoreapp3.1 - false - enable - false - true - true - false - - - - - - - - diff --git a/src/Tools/dotnet-watch/BrowserRefresh/src/ResponseStreamWrapper.cs b/src/Tools/dotnet-watch/BrowserRefresh/src/ResponseStreamWrapper.cs deleted file mode 100644 index ae5fcc6f2cc7..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/src/ResponseStreamWrapper.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -// Based on https://github.com/RickStrahl/Westwind.AspnetCore.LiveReload/blob/128b5f524e86954e997f2c453e7e5c1dcc3db746/Westwind.AspnetCore.LiveReload/ResponseStreamWrapper.cs - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; - -namespace Microsoft.AspNetCore.Watch.BrowserRefresh -{ - /// - /// Wraps the Response Stream to inject the WebSocket HTML into - /// an HTML Page. - /// - public class ResponseStreamWrapper : Stream - { - private static readonly MediaTypeHeaderValue _textHtmlMediaType = new MediaTypeHeaderValue("text/html"); - private readonly Stream _baseStream; - private readonly HttpContext _context; - private readonly ILogger _logger; - private bool? _isHtmlResponse; - - public ResponseStreamWrapper(HttpContext context, ILogger logger) - { - _context = context; - _baseStream = context.Response.Body; - _logger = logger; - } - - public override bool CanRead => false; - public override bool CanSeek => false; - public override bool CanWrite => true; - public override long Length { get; } - public override long Position { get; set; } - public bool ScriptInjectionPerformed { get; private set; } - - public bool IsHtmlResponse => _isHtmlResponse ?? false; - - public override void Flush() - { - OnWrite(); - _baseStream.Flush(); - } - - public override Task FlushAsync(CancellationToken cancellationToken) - { - OnWrite(); - return _baseStream.FlushAsync(cancellationToken); - } - - public override void Write(ReadOnlySpan buffer) - { - OnWrite(); - if (IsHtmlResponse && !ScriptInjectionPerformed) - { - ScriptInjectionPerformed = WebSocketScriptInjection.TryInjectLiveReloadScript(_baseStream, buffer); - } - else - { - _baseStream.Write(buffer); - } - } - - public override void WriteByte(byte value) - { - OnWrite(); - _baseStream.WriteByte(value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - OnWrite(); - - if (IsHtmlResponse && !ScriptInjectionPerformed) - { - ScriptInjectionPerformed = WebSocketScriptInjection.TryInjectLiveReloadScript(_baseStream, buffer.AsSpan(offset, count)); - } - else - { - _baseStream.Write(buffer, offset, count); - } - } - - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - OnWrite(); - - if (IsHtmlResponse && !ScriptInjectionPerformed) - { - ScriptInjectionPerformed = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(_baseStream, buffer.AsMemory(offset, count), cancellationToken); - } - else - { - await _baseStream.WriteAsync(buffer, offset, count, cancellationToken); - } - } - - public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - OnWrite(); - - if (IsHtmlResponse && !ScriptInjectionPerformed) - { - ScriptInjectionPerformed = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(_baseStream, buffer, cancellationToken); - } - else - { - await _baseStream.WriteAsync(buffer, cancellationToken); - } - } - - private void OnWrite() - { - if (_isHtmlResponse.HasValue) - { - return; - } - - var response = _context.Response; - - _isHtmlResponse = - (response.StatusCode == StatusCodes.Status200OK || response.StatusCode == StatusCodes.Status500InternalServerError) && - MediaTypeHeaderValue.TryParse(response.ContentType, out var mediaType) && - mediaType.IsSubsetOf(_textHtmlMediaType) && - (!mediaType.Charset.HasValue || mediaType.Charset.Equals("utf-8", StringComparison.OrdinalIgnoreCase)); - - if (_isHtmlResponse.Value) - { - BrowserRefreshMiddleware.Log.SetupResponseForBrowserRefresh(_logger); - - // Since we're changing the markup content, reset the content-length - response.Headers.ContentLength = null; - } - } - - public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => throw new NotSupportedException(); - - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - => throw new NotSupportedException(); - - public override void SetLength(long value) => throw new NotSupportedException(); - } -} diff --git a/src/Tools/dotnet-watch/BrowserRefresh/src/StartupHook.cs b/src/Tools/dotnet-watch/BrowserRefresh/src/StartupHook.cs deleted file mode 100644 index f0dbc50e61fa..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/src/StartupHook.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -internal class StartupHook -{ - public static void Initialize() - { - // This method exists to make startup hook load successfully. We do not need to do anything interesting here. - } -} diff --git a/src/Tools/dotnet-watch/BrowserRefresh/src/WebSocketScriptInjection.cs b/src/Tools/dotnet-watch/BrowserRefresh/src/WebSocketScriptInjection.cs deleted file mode 100644 index 43cb43c50ef5..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/src/WebSocketScriptInjection.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Watch.BrowserRefresh -{ - /// - /// Helper class that handles the HTML injection into - /// a string or byte array. - /// - public static class WebSocketScriptInjection - { - private const string BodyMarker = ""; - internal const string WebSocketScriptUrl = "/_framework/aspnetcore-browser-refresh.js"; - - private static readonly byte[] _bodyBytes = Encoding.UTF8.GetBytes(BodyMarker); - - internal static string InjectedScript { get; } = $""; - - private static readonly byte[] _injectedScriptBytes = Encoding.UTF8.GetBytes(InjectedScript); - - public static bool TryInjectLiveReloadScript(Stream baseStream, ReadOnlySpan buffer) - { - var index = buffer.LastIndexOf(_bodyBytes); - if (index == -1) - { - baseStream.Write(buffer); - return false; - } - - if (index > 0) - { - baseStream.Write(buffer.Slice(0, index)); - buffer = buffer[index..]; - } - - // Write the injected script - baseStream.Write(_injectedScriptBytes); - - // Write the rest of the buffer/HTML doc - baseStream.Write(buffer); - return true; - } - - public static async ValueTask TryInjectLiveReloadScriptAsync(Stream baseStream, ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - var index = buffer.Span.LastIndexOf(_bodyBytes); - if (index == -1) - { - await baseStream.WriteAsync(buffer, cancellationToken); - return false; - } - - if (index > 0) - { - await baseStream.WriteAsync(buffer.Slice(0, index), cancellationToken); - buffer = buffer[index..]; - } - - // Write the injected script - await baseStream.WriteAsync(_injectedScriptBytes, cancellationToken); - - // Write the rest of the buffer/HTML doc - await baseStream.WriteAsync(buffer, cancellationToken); - return true; - } - - } -} diff --git a/src/Tools/dotnet-watch/BrowserRefresh/src/WebSocketScriptInjection.js b/src/Tools/dotnet-watch/BrowserRefresh/src/WebSocketScriptInjection.js deleted file mode 100644 index 2de8e94cc36e..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/src/WebSocketScriptInjection.js +++ /dev/null @@ -1,63 +0,0 @@ -setTimeout(function () { - // dotnet-watch browser reload script - let connection; - try { - connection = new WebSocket('{{hostString}}'); - } catch (ex) { - console.debug(ex); - return; - } - connection.onmessage = function (message) { - const updateStaticFileMessage = 'UpdateStaticFile||'; - - if (message.data === 'Reload') { - console.debug('Server is ready. Reloading...'); - location.reload(); - } else if (message.data === 'Wait') { - console.debug('File changes detected. Waiting for application to rebuild.'); - const t = document.title; - const r = ['☱', '☲', '☴']; - let i = 0; - setInterval(function () { document.title = r[i++ % r.length] + ' ' + t; }, 240); - } else if (message.data.startsWith(updateStaticFileMessage)) { - const fileName = message.data.substring(updateStaticFileMessage.length); - if (!fileName.endsWith('.css')) { - console.debug(`File change detected to static content file ${fileName}. Reloading page...`); - location.reload(); - return; - } - - const styleElement = document.querySelector(`link[href^="${fileName}"]`) || - document.querySelector(`link[href^="${document.baseURI}${fileName}"]`); - if (styleElement && styleElement.parentNode) { - if (styleElement.loading) { - // A file change notification may be triggered for the same file before the browser - // finishes processing a previous update. In this case, it's easiest to ignore later updates - return; - } - - const newElement = styleElement.cloneNode(); - const href = styleElement.href; - newElement.href = href.split('?', 1)[0] + `?nonce=${Date.now()}`; - - styleElement.loading = true; - newElement.loading = true; - newElement.addEventListener('load', function () { - newElement.loading = false; - styleElement.remove(); - }); - - styleElement.parentNode.insertBefore(newElement, styleElement.nextSibling); - } else { - console.debug('Unable to find a stylesheet to update. Reloading the page.'); - location.reload(); - } - } else { - console.debug('Unknown browser-refresh message received: ', message.data); - } - } - - connection.onerror = function (event) { console.debug('dotnet-watch reload socket error.', event) } - connection.onclose = function () { console.debug('dotnet-watch reload socket closed.') } - connection.onopen = function () { console.debug('dotnet-watch reload socket connected.') } -}, 500); diff --git a/src/Tools/dotnet-watch/BrowserRefresh/test/BrowserRefreshMiddlewareTest.cs b/src/Tools/dotnet-watch/BrowserRefresh/test/BrowserRefreshMiddlewareTest.cs deleted file mode 100644 index 31610af9f4cb..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/test/BrowserRefreshMiddlewareTest.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Http; -using Xunit; - -namespace Microsoft.AspNetCore.Watch.BrowserRefresh -{ - public class BrowserRefreshMiddlewareTest - { - [Theory] - [InlineData("DELETE")] - [InlineData("head")] - [InlineData("Put")] - public void IsBrowserRequest_ReturnsFalse_ForNonGetOrPostRequests(string method) - { - // Arrange - var context = new DefaultHttpContext - { - Request = - { - Method = method, - Headers = - { - ["Accept"] = "application/html", - }, - }, - }; - - // Act - var result = BrowserRefreshMiddleware.IsBrowserRequest(context); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsBrowserRequest_ReturnsFalse_IsRequestDoesNotAcceptHtml() - { - // Arrange - var context = new DefaultHttpContext - { - Request = - { - Method = "GET", - Headers = - { - ["Accept"] = "application/xml", - }, - }, - }; - - // Act - var result = BrowserRefreshMiddleware.IsBrowserRequest(context); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsBrowserRequest_ReturnsTrue_ForGetRequestsThatAcceptHtml() - { - // Arrange - var context = new DefaultHttpContext - { - Request = - { - Method = "GET", - Headers = - { - ["Accept"] = "application/json,text/html;q=0.9", - }, - }, - }; - - // Act - var result = BrowserRefreshMiddleware.IsBrowserRequest(context); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsBrowserRequest_ReturnsTrue_ForRequestsThatAcceptAnyHtml() - { - // Arrange - var context = new DefaultHttpContext - { - Request = - { - Method = "Post", - Headers = - { - ["Accept"] = "application/json,text/*+html;q=0.9", - }, - }, - }; - - // Act - var result = BrowserRefreshMiddleware.IsBrowserRequest(context); - - // Assert - Assert.True(result); - } - } -} diff --git a/src/Tools/dotnet-watch/BrowserRefresh/test/BrowserScriptMiddlewareTest.cs b/src/Tools/dotnet-watch/BrowserRefresh/test/BrowserScriptMiddlewareTest.cs deleted file mode 100644 index 92931158e067..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/test/BrowserScriptMiddlewareTest.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Xunit; - -namespace Microsoft.AspNetCore.Watch.BrowserRefresh -{ - public class BrowserScriptMiddlewareTest - { - [Fact] - public async Task InvokeAsync_ReturnsScript() - { - // Arrange - var context = new DefaultHttpContext(); - var stream = new MemoryStream(); - context.Response.Body = stream; - var middleware = new BrowserScriptMiddleware("some-host"); - - // Act - await middleware.InvokeAsync(context); - - // Assert - stream.Position = 0; - var script = Encoding.UTF8.GetString(stream.ToArray()); - Assert.Contains("// dotnet-watch browser reload script", script); - Assert.Contains("'some-host'", script); - } - - [Fact] - public async Task InvokeAsync_ConfiguresHeaders() - { - // Arrange - var context = new DefaultHttpContext(); - context.Response.Body = new MemoryStream(); - var middleware = new BrowserScriptMiddleware("some-host"); - - // Act - await middleware.InvokeAsync(context); - - // Assert - var response = context.Response; - Assert.Collection( - response.Headers.OrderBy(h => h.Key), - kvp => - { - Assert.Equal("Cache-Control", kvp.Key); - Assert.Equal("no-store", kvp.Value); - }, - kvp => - { - Assert.Equal("Content-Length", kvp.Key); - Assert.NotEmpty(kvp.Value); - }, - kvp => - { - Assert.Equal("Content-Type", kvp.Key); - Assert.Equal("application/javascript; charset=utf-8", kvp.Value); - }); - } - } -} diff --git a/src/Tools/dotnet-watch/BrowserRefresh/test/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests.csproj b/src/Tools/dotnet-watch/BrowserRefresh/test/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests.csproj deleted file mode 100644 index 5b3938e42668..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/test/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - $(DefaultNetCoreTargetFramework) - Microsoft.AspNetCore.Watch.BrowserRefresh - enable - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Tools/dotnet-watch/BrowserRefresh/test/ResponseStreamWrapperTest.cs b/src/Tools/dotnet-watch/BrowserRefresh/test/ResponseStreamWrapperTest.cs deleted file mode 100644 index f8c48ba879a1..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/test/ResponseStreamWrapperTest.cs +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Hosting; -using Xunit; - -namespace Microsoft.AspNetCore.Watch.BrowserRefresh.Tests -{ - public class ResponseStreamWrapperTest - { - private const string BrowserAcceptHeader = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"; - - [Fact] - public async Task HtmlIsInjectedForStaticFiles() - { - // Arrange - using var host = await StartHostAsync(); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/index.html").AddHeader("Accept", BrowserAcceptHeader).SendAsync("GET"); - - // Assert - response.EnsureSuccessStatusCode(); - - var content = await response.Content.ReadAsStringAsync(); - Assert.Contains(WebSocketScriptInjection.InjectedScript, content); - } - - [Fact] - public async Task HtmlIsInjectedForLargeStaticFiles() - { - // Arrange - using var host = await StartHostAsync(); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/largefile.html").AddHeader("Accept", BrowserAcceptHeader).SendAsync("GET"); - - // Assert - response.EnsureSuccessStatusCode(); - - Assert.False(response.Content.Headers.TryGetValues("Content-Length", out _), "We shouldn't send a Content-Length header."); - var content = await response.Content.ReadAsStringAsync(); - Assert.Contains(WebSocketScriptInjection.InjectedScript, content); - } - - [Fact] - public async Task HtmlIsInjectedForDynamicallyGeneratedMarkup() - { - // Arrange - using var host = await StartHostAsync(routes => - { - routes.MapGet("/dynamic-html", async context => - { - context.Response.Headers["Content-Type"] = "text/html;charset=utf-8"; - await context.Response.WriteAsync("

Hello world

"); - }); - }); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/dynamic-html").AddHeader("Accept", BrowserAcceptHeader).SendAsync("GET"); - - // Assert - response.EnsureSuccessStatusCode(); - - Assert.False(response.Content.Headers.TryGetValues("Content-Length", out _), "We shouldn't send a Content-Length header."); - var content = await response.Content.ReadAsStringAsync(); - Assert.Contains(WebSocketScriptInjection.InjectedScript, content); - } - - [Fact] - public async Task HtmlIsInjectedForWriteAsyncMarkupWithContentLength() - { - // Arrange - var responseContent = Encoding.UTF8.GetBytes("

Hello world

"); - using var host = await StartHostAsync(routes => - { - routes.MapGet("/dynamic-html", async context => - { - context.Response.ContentLength = responseContent.Length; - context.Response.Headers["Content-Type"] = "text/html;charset=utf-8"; - await context.Response.Body.WriteAsync(responseContent); - }); - }); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/dynamic-html").AddHeader("Accept", BrowserAcceptHeader).SendAsync("GET"); - - // Assert - response.EnsureSuccessStatusCode(); - - Assert.False(response.Content.Headers.TryGetValues("Content-Length", out _), "We shouldn't send a Content-Length header."); - var content = await response.Content.ReadAsStringAsync(); - Assert.Contains(WebSocketScriptInjection.InjectedScript, content); - } - - [Fact] - public async Task HtmlIsInjectedForWriteAsyncMarkupWithMultipleWrites() - { - // Arrange - using var host = await StartHostAsync(routes => - { - routes.MapGet("/dynamic-html", async context => - { - context.Response.Headers["Content-Type"] = "text/html;charset=utf-8"; - - await context.Response.WriteAsync(""); - await context.Response.WriteAsync(""); - await context.Response.WriteAsync("
    "); - for (var i = 0; i < 100; i++) - { - await context.Response.WriteAsync($"
  • {i}
  • "); - } - await context.Response.WriteAsync("
"); - await context.Response.WriteAsync(""); - await context.Response.WriteAsync(""); - }); - }); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/dynamic-html").AddHeader("Accept", BrowserAcceptHeader).SendAsync("GET"); - - // Assert - response.EnsureSuccessStatusCode(); - - Assert.False(response.Content.Headers.TryGetValues("Content-Length", out _), "We shouldn't send a Content-Length header."); - var content = await response.Content.ReadAsStringAsync(); - Assert.Contains(WebSocketScriptInjection.InjectedScript, content); - } - - [Fact] - public async Task HtmlIsInjectedForPostResponses() - { - // Arrange - using var host = await StartHostAsync(routes => - { - routes.MapPost("/mvc-view", async context => - { - context.Response.Headers["Content-Type"] = "text/html;charset=utf-8"; - - await context.Response.WriteAsync(""); - await context.Response.WriteAsync(""); - await context.Response.WriteAsync("
    "); - for (var i = 0; i < 100; i++) - { - await context.Response.WriteAsync($"
  • {i}
  • "); - } - await context.Response.WriteAsync("
"); - await context.Response.WriteAsync(""); - await context.Response.WriteAsync(""); - }); - }); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/mvc-view").AddHeader("Accept", BrowserAcceptHeader).SendAsync("POST"); - - // Assert - response.EnsureSuccessStatusCode(); - - Assert.False(response.Content.Headers.TryGetValues("Content-Length", out _), "We shouldn't send a Content-Length header."); - var content = await response.Content.ReadAsStringAsync(); - Assert.Contains(WebSocketScriptInjection.InjectedScript, content); - } - - [Fact] - public async Task HtmlIsNotInjectedForNonBrowserRequests() - { - // Arrange - using var host = await StartHostAsync(); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/favicon.ico").AddHeader("Accept", "application/json").SendAsync("GET"); - - // Assert - response.EnsureSuccessStatusCode(); - - var content = await response.Content.ReadAsStringAsync(); - Assert.DoesNotContain("dotnet-watch browser reload script", content); - } - - [Fact] - public async Task HtmlIsNotInjectedForNonHtmlResponses() - { - // Arrange - using var host = await StartHostAsync(); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/favicon.ico").AddHeader("Accept", BrowserAcceptHeader).SendAsync("GET"); - - // Assert - response.EnsureSuccessStatusCode(); - - var content = await response.Content.ReadAsStringAsync(); - Assert.DoesNotContain("dotnet-watch browser reload script", content); - } - - [Fact] - public async Task HtmlIsNotInjectedForNon200Responses() - { - // Arrange - using var host = await StartHostAsync(); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/file-does-not-exist.html").AddHeader("Accept", BrowserAcceptHeader).SendAsync("GET"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - - var content = await response.Content.ReadAsStringAsync(); - Assert.DoesNotContain("dotnet-watch browser reload script", content); - } - - [Fact] - public async Task HtmlIsNotInjectedForNonGetOrPostResponses() - { - // Arrange - var responseContent = "

Hello world

"; - using var host = await StartHostAsync(routes => - { - routes.MapMethods( - "/dynamic-html", - new[] { "HEAD", "GET", "DELETE" }, - async context => - { - context.Response.Headers["Content-Type"] = "text/html;charset=utf-8"; - await context.Response.WriteAsync(responseContent); - }); - }); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/dynamic-html").AddHeader("Accept", BrowserAcceptHeader).SendAsync("HEAD"); - - // Assert - response.EnsureSuccessStatusCode(); - - var content = await response.Content.ReadAsStringAsync(); - Assert.DoesNotContain("dotnet-watch browser reload script", content); - } - - [Fact] - public async Task HtmlIsNotInjectedForJsonResponses() - { - // Arrange - var responseContent = "

Hello world

"; - using var host = await StartHostAsync(routes => - { - routes.MapGet( - "/dynamic-html", - async context => - { - context.Response.Headers["Content-Type"] = "application/json;charset=utf-8"; - await context.Response.WriteAsync(responseContent); - }); - }); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/dynamic-html").AddHeader("Accept", BrowserAcceptHeader).SendAsync("GET"); - - // Assert - response.EnsureSuccessStatusCode(); - - var content = await response.Content.ReadAsStringAsync(); - Assert.DoesNotContain("dotnet-watch browser reload script", content); - } - - [Fact] - public async Task HtmlIsNotInjectedForNonUtf8Responses() - { - // Arrange - var responseContent = "

Hello world

"; - using var host = await StartHostAsync(routes => - { - routes.MapGet( - "/dynamic-html", - async context => - { - context.Response.Headers["Content-Type"] = "text/html;charset=utf-16"; - await context.Response.Body.WriteAsync(Encoding.Unicode.GetBytes(responseContent)); - }); - }); - using var server = host.GetTestServer(); - var response = await server.CreateRequest("/dynamic-html").AddHeader("Accept", BrowserAcceptHeader).SendAsync("GET"); - - // Assert - response.EnsureSuccessStatusCode(); - - var content = await response.Content.ReadAsStringAsync(); - Assert.DoesNotContain("dotnet-watch browser reload script", content); - } - - private static async Task StartHostAsync(Action? routeBuilder = null) - { - var host = new HostBuilder() - .ConfigureWebHost(webHostBuilder => - { - webHostBuilder - .UseTestServer() - .Configure(app => - { - app.UseMiddleware(); - app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, "wwwroot")) }); - - if (routeBuilder != null) - { - app.UseRouting(); - app.UseEndpoints(routeBuilder); - } - }) - .ConfigureServices(services => services.AddRouting()); - }).Build(); - - await host.StartAsync(); - return host; - } - } -} diff --git a/src/Tools/dotnet-watch/BrowserRefresh/test/WebSockerScriptInjectionTest.cs b/src/Tools/dotnet-watch/BrowserRefresh/test/WebSockerScriptInjectionTest.cs deleted file mode 100644 index 6ad204ae4a55..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/test/WebSockerScriptInjectionTest.cs +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.AspNetCore.Watch.BrowserRefresh -{ - public class WebSockerScriptInjectionTest - { - [Fact] - public async Task TryInjectLiveReloadScriptAsync_DoesNotInjectMarkup_IfInputDoesNotContainBodyTag() - { - // Arrange - var stream = new MemoryStream(); - var input = Encoding.UTF8.GetBytes("
this is not a real body tag.
"); - - // Act - var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input); - - // Assert - Assert.False(result); - Assert.Equal(input, stream.ToArray()); - } - - [Fact] - public async Task TryInjectLiveReloadScriptAsync_InjectsMarkupIfBodyTagAppearsInTheMiddle() - { - // Arrange - var expected = -$@"
- This is the footer -
-{WebSocketScriptInjection.InjectedScript} -"; - var stream = new MemoryStream(); - var input = Encoding.UTF8.GetBytes( -@"
- This is the footer -
- -"); - - // Act - var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input); - - // Assert - Assert.True(result); - var output = Encoding.UTF8.GetString(stream.ToArray()); - Assert.Equal(expected, output, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task TryInjectLiveReloadScriptAsync_WithOffsetBodyTagAppearsInMiddle() - { - // Arrange - var expected = $"{WebSocketScriptInjection.InjectedScript}"; - var stream = new MemoryStream(); - var input = Encoding.UTF8.GetBytes("unused"); - - // Act - var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input.AsMemory(6)); - - // Assert - Assert.True(result); - var output = Encoding.UTF8.GetString(stream.ToArray()); - Assert.Equal(expected, output); - } - - [Fact] - public async Task TryInjectLiveReloadScriptAsync_WithOffsetBodyTagAppearsAtStartOfOffset() - { - // Arrange - var expected = $"{WebSocketScriptInjection.InjectedScript}"; - var stream = new MemoryStream(); - var input = Encoding.UTF8.GetBytes("unused"); - - // Act - var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input.AsMemory(6)); - - // Assert - Assert.True(result); - var output = Encoding.UTF8.GetString(stream.ToArray()); - Assert.Equal(expected, output); - } - - [Fact] - public async Task TryInjectLiveReloadScriptAsync_InjectsMarkupIfBodyTagAppearsAtTheStartOfOutput() - { - // Arrange - var expected = $"{WebSocketScriptInjection.InjectedScript}"; - var stream = new MemoryStream(); - var input = Encoding.UTF8.GetBytes(""); - - // Act - var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input); - - // Assert - Assert.True(result); - var output = Encoding.UTF8.GetString(stream.ToArray()); - Assert.Equal(expected, output); - } - - [Fact] - public async Task TryInjectLiveReloadScriptAsync_InjectsMarkupIfBodyTagAppearsByItself() - { - // Arrange - var expected = $"{WebSocketScriptInjection.InjectedScript}"; - var stream = new MemoryStream(); - var input = Encoding.UTF8.GetBytes(""); - - // Act - var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input); - - // Assert - Assert.True(result); - var output = Encoding.UTF8.GetString(stream.ToArray()); - Assert.Equal(expected, output); - } - - [Fact] - public async Task TryInjectLiveReloadScriptAsync_MultipleBodyTags() - { - // Arrange - var expected = $"

some text

{WebSocketScriptInjection.InjectedScript}"; - var stream = new MemoryStream(); - var input = Encoding.UTF8.GetBytes("abc

some text

").AsMemory(3); - - // Act - var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input); - - // Assert - Assert.True(result); - var output = Encoding.UTF8.GetString(stream.ToArray()); - Assert.Equal(expected, output); - } - - [Fact] - public void TryInjectLiveReloadScript_NoBodyTag() - { - // Arrange - var expected = "

Hello world

"; - var stream = new MemoryStream(); - var input = Encoding.UTF8.GetBytes(expected).AsSpan(); - - // Act - var result = WebSocketScriptInjection.TryInjectLiveReloadScript(stream, input); - - // Assert - Assert.False(result); - var output = Encoding.UTF8.GetString(stream.ToArray()); - Assert.Equal(expected, output); - } - - [Fact] - public void TryInjectLiveReloadScript_NoOffset() - { - // Arrange - var expected = $"{WebSocketScriptInjection.InjectedScript}"; - var stream = new MemoryStream(); - var input = Encoding.UTF8.GetBytes("").AsSpan(); - - // Act - var result = WebSocketScriptInjection.TryInjectLiveReloadScript(stream, input); - - // Assert - Assert.True(result); - var output = Encoding.UTF8.GetString(stream.ToArray()); - Assert.Equal(expected, output); - } - - [Fact] - public void TryInjectLiveReloadScript_WithOffset() - { - // Arrange - var expected = $"{WebSocketScriptInjection.InjectedScript}"; - var stream = new MemoryStream(); - var input = Encoding.UTF8.GetBytes("unused").AsSpan(6); - - // Act - var result = WebSocketScriptInjection.TryInjectLiveReloadScript(stream, input); - - // Assert - Assert.True(result); - var output = Encoding.UTF8.GetString(stream.ToArray()); - Assert.Equal(expected, output); - } - } -} diff --git a/src/Tools/dotnet-watch/BrowserRefresh/test/wwwroot/favicon.ico b/src/Tools/dotnet-watch/BrowserRefresh/test/wwwroot/favicon.ico deleted file mode 100644 index 63e859b476eff5055e0e557aaa151ca8223fbeef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5430 zcmc&&Yj2xp8Fqnv;>&(QB_ve7>^E#o2mu=cO~A%R>DU-_hfbSRv1t;m7zJ_AMrntN zy0+^f&8be>q&YYzH%(88lQ?#KwiCzaCO*ZEo%j&v;<}&Lj_stKTKK>#U3nin@AF>w zb3ONSAFR{u(S1d?cdw53y}Gt1b-Hirbh;;bm(Rcbnoc*%@jiaXM|4jU^1WO~`TYZ~ zC-~jh9~b-f?fX`DmwvcguQzn*uV}c^Vd&~?H|RUs4Epv~gTAfR(B0lT&?RWQOtduM z^1vUD9{HQsW!{a9|0crA34m7Z6lpG^}f6f?={zD+ zXAzk^i^aKN_}s2$eX81wjSMONE#WVdzf|MT)Ap*}Vsn!XbvsI#6o&ij{87^d%$|A{ z=F{KB%)g%@z76yBzbb7seW**Ju8r4e*Z3PWNX3_tTDgzZatz7)Q6ytwB%@&@A|XT; zecM`Snxx5po$C)%yCP!KEtos~eOS)@2=kX-RIm)4glMCoagTEFxrBeSX%Euz734Fk z%7)x(k~T!@Hbg_37NSQL!vlTBXoURSzt~I**Zw`&F24fH*&kx=%nvZv|49SC*daD( zIw<~%#=lk8{2-l(BcIjy^Q$Q&m#KlWL9?UG{b8@qhlD z;umc+6p%|NsAT~0@DgV4-NKgQuWPWrmPIK&&XhV&n%`{l zOl^bbWYjQNuVXTXESO)@|iUKVmErPUDfz2Wh`4dF@OFiaCW|d`3paV^@|r^8T_ZxM)Z+$p5qx# z#K=z@%;aBPO=C4JNNGqVv6@UGolIz;KZsAro``Rz8X%vq_gpi^qEV&evgHb_=Y9-l z`)imdx0UC>GWZYj)3+3aKh?zVb}=@%oNzg7a8%kfVl)SV-Amp1Okw&+hEZ3|v(k8vRjXW9?ih`&FFM zV$~{j3IzhtcXk?Mu_!12;=+I7XK-IR2>Yd%VB^?oI9c^E&Chb&&je$NV0P-R;ujkP z;cbLCCPEF6|22NDj=S`F^2e~XwT1ZnRX8ra0#DaFa9-X|8(xNW_+JhD75WnSd7cxo z2>I_J5{c|WPfrgl7E2R)^c}F7ry()Z>$Jhk9CzZxiPKL#_0%`&{MX>P_%b~Dx0D^S z7xP1(DQ!d_Icpk!RN3I1w@~|O1ru#CO==h#9M~S4Chx*@?=EKUPGBv$tmU+7Zs_al z`!jR?6T&Z7(%uVq>#yLu`abWk!FBlnY{RFNHlj~6zh*;@u}+}viRKsD`IIxN#R-X3 z@vxu#EA_m}I503U(8Qmx^}u;)KfGP`O9E1H1Q|xeeksX8jC%@!{YT1)!lWgO=+Y3*jr=iSxvOW1}^HSy=y){tOMQJ@an>sOl4FYniE z;GOxd7AqxZNbYFNqobpv&HVO$c-w!Y*6r;$2oJ~h(a#(Bp<-)dg*mNigX~9rPqcHv z^;c*|Md?tD)$y?6FO$DWl$jUGV`F1G_^E&E>sY*YnA~ruv3=z9F8&&~Xpm<<75?N3 z>x~`I&M9q)O1=zWZHN9hZWx>RQ}zLP+iL57Q)%&_^$Sme^^G7;e-P~CR?kqU#Io#( z(nH1Wn*Ig)|M>WLGrxoU?FZrS`4GO&w;+39A3f8w{{Q7eg|$+dIlNFPAe+tN=FOYU z{A&Fg|H73+w1IK(W=j*L>JQgz$g0 z7JpKXLHIh}#$wm|N`s}o-@|L_`>*(gTQ~)wr3Eap7g%PVNisKw82im;Gdv#85x#s+ zoqqtnwu4ycd>cOQgRh-=aEJbnvVK`}ja%+FZx}&ehtX)n(9nVfe4{mn0bgijUbNr7Tf5X^$*{qh2%`?--%+sbSrjE^;1e3>% zqa%jdY16{Y)a1hSy*mr0JGU05Z%=qlx5vGvTjSpTt6k%nR06q}1DU`SQh_ZAeJ}A@`hL~xvv05U?0%=spP`R>dk?cOWM9^KNb7B?xjex>OZo%JMQQ1Q zB|q@}8RiP@DWn-(fB;phPaIOP2Yp)XN3-Fsn)S3w($4&+p8f5W_f%gac}QvmkHfCj$2=!t`boCvQ zCW;&Dto=f8v##}dy^wg3VNaBy&kCe3N;1|@n@pUaMPT?(aJ9b*(gJ28$}(2qFt$H~u5z94xcIQkcOI++)*exzbrk?WOOOf*|%k5#KV zL=&ky3)Eirv$wbRJ2F2s_ILQY--D~~7>^f}W|Aw^e7inXr#WLI{@h`0|jHud2Y~cI~Yn{r_kU^Vo{1gja - - - - - - - -

Hello world

- - diff --git a/src/Tools/dotnet-watch/BrowserRefresh/test/wwwroot/largefile.html b/src/Tools/dotnet-watch/BrowserRefresh/test/wwwroot/largefile.html deleted file mode 100644 index b43e8b1c7e1f..000000000000 --- a/src/Tools/dotnet-watch/BrowserRefresh/test/wwwroot/largefile.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - -

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. At consectetur lorem donec massa sapien faucibus et. Tristique senectus et netus et malesuada fames. A lacus vestibulum sed arcu. At varius vel pharetra vel turpis nunc eget lorem. Quam pellentesque nec nam aliquam. Et ultrices neque ornare aenean euismod elementum. Nullam eget felis eget nunc lobortis mattis aliquam faucibus purus. Laoreet id donec ultrices tincidunt arcu non sodales neque. Ac placerat vestibulum lectus mauris ultrices eros in cursus. Volutpat sed cras ornare arcu dui. Elit eget gravida cum sociis natoque. Vitae tempus quam pellentesque nec nam aliquam sem. Arcu bibendum at varius vel pharetra. Lectus quam id leo in vitae.

-

In dictum non consectetur a erat nam at. Dictum fusce ut placerat orci nulla. Aliquam malesuada bibendum arcu vitae elementum curabitur. Ornare aenean euismod elementum nisi quis eleifend quam. Varius duis at consectetur lorem donec massa sapien. Volutpat commodo sed egestas egestas fringilla phasellus faucibus scelerisque eleifend. Egestas sed tempus urna et. Dolor morbi non arcu risus quis varius quam quisque. Platea dictumst vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras. Purus viverra accumsan in nisl. Lobortis feugiat vivamus at augue eget arcu dictum varius. Mauris cursus mattis molestie a. Tortor at auctor urna nunc id. Suscipit tellus mauris a diam maecenas. Aenean pharetra magna ac placerat vestibulum. Donec ac odio tempor orci. At tempor commodo ullamcorper a lacus vestibulum sed. Sed vulputate odio ut enim blandit. Massa vitae tortor condimentum lacinia. Pulvinar etiam non quam lacus suspendisse faucibus interdum.

-

Enim eu turpis egestas pretium. Mus mauris vitae ultricies leo integer malesuada nunc vel. Massa sed elementum tempus egestas sed sed risus pretium quam. In metus vulputate eu scelerisque felis imperdiet proin. Faucibus scelerisque eleifend donec pretium vulputate sapien. Arcu non odio euismod lacinia at quis. Facilisis magna etiam tempor orci eu. Morbi non arcu risus quis varius quam quisque id diam. Quis eleifend quam adipiscing vitae proin. Ut placerat orci nulla pellentesque dignissim enim. Aliquet sagittis id consectetur purus ut faucibus pulvinar elementum. Nunc aliquet bibendum enim facilisis. Non tellus orci ac auctor augue mauris augue. Venenatis urna cursus eget nunc. Amet porttitor eget dolor morbi non. Fames ac turpis egestas integer eget aliquet nibh praesent tristique. Orci porta non pulvinar neque laoreet suspendisse interdum consectetur.

-

Volutpat diam ut venenatis tellus in. Odio pellentesque diam volutpat commodo. Diam quam nulla porttitor massa id. Ultricies leo integer malesuada nunc vel. Auctor elit sed vulputate mi sit. Sit amet est placerat in egestas erat. Id eu nisl nunc mi ipsum. Vulputate ut pharetra sit amet aliquam. Justo laoreet sit amet cursus sit amet. Felis donec et odio pellentesque diam volutpat commodo. Sit amet risus nullam eget felis. Vulputate mi sit amet mauris commodo quis imperdiet. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus. Egestas maecenas pharetra convallis posuere morbi leo urna molestie at. Pulvinar mattis nunc sed blandit. Auctor neque vitae tempus quam pellentesque nec nam.

-

In metus vulputate eu scelerisque felis imperdiet proin fermentum. Netus et malesuada fames ac turpis egestas integer eget. Sit amet massa vitae tortor condimentum lacinia. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel. Venenatis a condimentum vitae sapien pellentesque habitant morbi tristique. Vestibulum lectus mauris ultrices eros in cursus turpis massa tincidunt. Ut aliquam purus sit amet luctus venenatis lectus magna. Sed adipiscing diam donec adipiscing tristique risus nec. Ut porttitor leo a diam. Sem integer vitae justo eget magna fermentum.

-

Pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Leo a diam sollicitudin tempor id eu nisl nunc. Fames ac turpis egestas sed. Velit euismod in pellentesque massa placerat duis ultricies lacus sed. Enim nunc faucibus a pellentesque sit amet porttitor eget. Scelerisque felis imperdiet proin fermentum leo vel orci. Adipiscing tristique risus nec feugiat in. Tortor id aliquet lectus proin nibh nisl. Tortor vitae purus faucibus ornare. Sit amet luctus venenatis lectus magna fringilla urna porttitor rhoncus. Commodo elit at imperdiet dui. Nulla pellentesque dignissim enim sit. Orci a scelerisque purus semper eget duis at tellus at. Non curabitur gravida arcu ac. Libero enim sed faucibus turpis in eu mi.

-

A arcu cursus vitae congue. In mollis nunc sed id semper risus. Vestibulum rhoncus est pellentesque elit ullamcorper. Pellentesque sit amet porttitor eget. Quis auctor elit sed vulputate mi sit amet mauris. Imperdiet proin fermentum leo vel. Suspendisse interdum consectetur libero id faucibus nisl tincidunt eget. Ut tristique et egestas quis ipsum suspendisse ultrices gravida dictum. Feugiat scelerisque varius morbi enim nunc faucibus. Vitae nunc sed velit dignissim sodales. Quis varius quam quisque id. Dignissim convallis aenean et tortor at risus viverra adipiscing. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus. At risus viverra adipiscing at in tellus integer feugiat. In fermentum et sollicitudin ac orci. Et ligula ullamcorper malesuada proin libero. Est placerat in egestas erat imperdiet. Nunc vel risus commodo viverra maecenas accumsan. Dui id ornare arcu odio ut.

-

Amet cursus sit amet dictum sit. Egestas sed tempus urna et pharetra pharetra massa. At imperdiet dui accumsan sit. Tempus iaculis urna id volutpat lacus laoreet non curabitur gravida. Morbi leo urna molestie at elementum. Eros in cursus turpis massa tincidunt dui ut ornare. Lorem donec massa sapien faucibus. A cras semper auctor neque vitae tempus quam. Libero enim sed faucibus turpis in. Vitae semper quis lectus nulla at. Tempor id eu nisl nunc mi. Ut lectus arcu bibendum at varius vel pharetra vel. Massa enim nec dui nunc mattis enim.

-

Quam nulla porttitor massa id. Cursus vitae congue mauris rhoncus aenean. Purus viverra accumsan in nisl nisi scelerisque eu. Tincidunt eget nullam non nisi est sit amet facilisis. Ac feugiat sed lectus vestibulum. Nisl purus in mollis nunc sed id semper risus in. Nunc scelerisque viverra mauris in aliquam sem fringilla. Diam maecenas ultricies mi eget mauris pharetra et ultrices neque. In ornare quam viverra orci sagittis eu volutpat odio facilisis. Ultricies tristique nulla aliquet enim tortor at auctor. Ipsum a arcu cursus vitae congue mauris rhoncus.

-

Massa massa ultricies mi quis hendrerit dolor. Iaculis at erat pellentesque adipiscing. Ut tellus elementum sagittis vitae et leo duis ut diam. Tellus in metus vulputate eu. Ac auctor augue mauris augue. Nunc pulvinar sapien et ligula ullamcorper malesuada proin libero. In massa tempor nec feugiat nisl pretium fusce. Lorem ipsum dolor sit amet consectetur adipiscing. Facilisis mauris sit amet massa vitae tortor condimentum lacinia quis. Et leo duis ut diam quam. Accumsan in nisl nisi scelerisque eu ultrices. Et netus et malesuada fames. Orci sagittis eu volutpat odio facilisis.

-

Posuere ac ut consequat semper viverra nam libero. In aliquam sem fringilla ut. Id nibh tortor id aliquet lectus proin nibh nisl. Sed viverra ipsum nunc aliquet bibendum. Pretium fusce id velit ut tortor pretium viverra suspendisse potenti. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Pharetra magna ac placerat vestibulum lectus mauris. Commodo elit at imperdiet dui. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Turpis massa tincidunt dui ut ornare lectus sit amet. A scelerisque purus semper eget duis. Amet nisl purus in mollis nunc sed id semper. Bibendum arcu vitae elementum curabitur vitae nunc sed velit dignissim. Ante in nibh mauris cursus mattis molestie a iaculis at. Cras tincidunt lobortis feugiat vivamus at augue. Consectetur purus ut faucibus pulvinar elementum integer. Netus et malesuada fames ac turpis egestas maecenas pharetra. At tempor commodo ullamcorper a lacus vestibulum sed arcu non.

-

Consectetur adipiscing elit duis tristique sollicitudin. Nunc id cursus metus aliquam eleifend. Id velit ut tortor pretium viverra suspendisse potenti nullam. Cursus turpis massa tincidunt dui ut ornare lectus. Nulla posuere sollicitudin aliquam ultrices. Viverra accumsan in nisl nisi scelerisque. Pulvinar etiam non quam lacus suspendisse. Velit euismod in pellentesque massa placerat duis ultricies lacus. Quis auctor elit sed vulputate. Gravida cum sociis natoque penatibus et magnis.

-

Fermentum posuere urna nec tincidunt praesent semper feugiat nibh. Et leo duis ut diam quam nulla porttitor. Suspendisse interdum consectetur libero id faucibus nisl. Elit duis tristique sollicitudin nibh sit amet. Et egestas quis ipsum suspendisse ultrices gravida dictum fusce. Eu facilisis sed odio morbi. Tellus pellentesque eu tincidunt tortor. Id porta nibh venenatis cras sed felis eget velit. Massa tincidunt dui ut ornare lectus sit. Venenatis tellus in metus vulputate. Egestas quis ipsum suspendisse ultrices gravida dictum fusce ut. Aliquet lectus proin nibh nisl condimentum id venenatis a condimentum. Id ornare arcu odio ut. Donec massa sapien faucibus et. Et leo duis ut diam quam nulla porttitor massa. Id venenatis a condimentum vitae sapien pellentesque habitant morbi. Sed id semper risus in. Vitae nunc sed velit dignissim sodales ut eu. Euismod elementum nisi quis eleifend quam. Aliquet enim tortor at auctor urna nunc id cursus.

-

Elementum nibh tellus molestie nunc non blandit massa. In ornare quam viverra orci sagittis. Sapien faucibus et molestie ac feugiat sed lectus vestibulum mattis. Ut eu sem integer vitae justo eget magna fermentum iaculis. Vel eros donec ac odio tempor. Venenatis a condimentum vitae sapien pellentesque habitant morbi tristique senectus. Pellentesque pulvinar pellentesque habitant morbi tristique senectus. Cursus turpis massa tincidunt dui ut. Tristique et egestas quis ipsum suspendisse ultrices. Nunc consequat interdum varius sit amet mattis vulputate enim. Nulla malesuada pellentesque elit eget gravida cum. Eget aliquet nibh praesent tristique magna sit amet. Malesuada pellentesque elit eget gravida cum sociis natoque penatibus et. Sagittis vitae et leo duis ut diam quam nulla. Venenatis lectus magna fringilla urna porttitor. In pellentesque massa placerat duis ultricies lacus sed.

-

Odio facilisis mauris sit amet massa vitae tortor condimentum lacinia. Platea dictumst quisque sagittis purus sit amet. Pharetra convallis posuere morbi leo urna molestie at. Nullam eget felis eget nunc lobortis mattis. Cras semper auctor neque vitae tempus quam pellentesque nec nam. Porttitor rhoncus dolor purus non enim. Lorem ipsum dolor sit amet. Urna molestie at elementum eu facilisis. Magna eget est lorem ipsum dolor. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet consectetur. Ipsum faucibus vitae aliquet nec ullamcorper sit amet risus nullam. Elementum sagittis vitae et leo duis ut diam quam nulla. Dolor morbi non arcu risus quis varius quam quisque. Nunc aliquet bibendum enim facilisis gravida neque convallis a.

-

Tortor consequat id porta nibh venenatis cras sed felis. Congue eu consequat ac felis donec. Risus at ultrices mi tempus. Et magnis dis parturient montes nascetur ridiculus mus mauris vitae. Malesuada proin libero nunc consequat interdum varius sit amet mattis. Turpis tincidunt id aliquet risus feugiat in ante. Lectus proin nibh nisl condimentum id venenatis. Aliquam ultrices sagittis orci a. Netus et malesuada fames ac turpis. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Arcu ac tortor dignissim convallis aenean et tortor at. Dignissim sodales ut eu sem integer. Auctor urna nunc id cursus metus aliquam eleifend. Nulla facilisi morbi tempus iaculis urna id volutpat. Bibendum ut tristique et egestas quis ipsum suspendisse. Magna etiam tempor orci eu lobortis elementum nibh tellus molestie. Magna fermentum iaculis eu non diam phasellus vestibulum lorem.

-

Laoreet non curabitur gravida arcu. Egestas tellus rutrum tellus pellentesque. Platea dictumst vestibulum rhoncus est pellentesque elit. Praesent elementum facilisis leo vel fringilla est ullamcorper. Augue eget arcu dictum varius duis at consectetur lorem donec. Vulputate enim nulla aliquet porttitor lacus. Volutpat commodo sed egestas egestas fringilla phasellus faucibus. Non curabitur gravida arcu ac tortor dignissim convallis aenean. Amet nisl purus in mollis nunc. Feugiat in ante metus dictum at tempor commodo ullamcorper a. Mauris cursus mattis molestie a iaculis at erat. Amet porttitor eget dolor morbi non arcu risus. Velit aliquet sagittis id consectetur purus ut faucibus. Pulvinar etiam non quam lacus suspendisse faucibus interdum. Euismod nisi porta lorem mollis aliquam ut porttitor. Vulputate odio ut enim blandit volutpat maecenas. Commodo nulla facilisi nullam vehicula ipsum a. Nunc vel risus commodo viverra maecenas accumsan lacus vel facilisis.

-

Fringilla ut morbi tincidunt augue interdum velit euismod in pellentesque. Arcu risus quis varius quam quisque id. Amet est placerat in egestas erat. Cras sed felis eget velit aliquet. Netus et malesuada fames ac turpis egestas. Quis varius quam quisque id diam vel quam elementum pulvinar. Diam vel quam elementum pulvinar. Mattis rhoncus urna neque viverra justo. Tincidunt eget nullam non nisi. Velit dignissim sodales ut eu sem integer vitae. Feugiat in fermentum posuere urna nec tincidunt. Quam pellentesque nec nam aliquam sem et tortor. In nisl nisi scelerisque eu ultrices vitae auctor eu augue.

-

Commodo sed egestas egestas fringilla phasellus faucibus scelerisque. Non diam phasellus vestibulum lorem sed risus ultricies. Dignissim convallis aenean et tortor at. Purus sit amet volutpat consequat mauris nunc congue nisi. Faucibus nisl tincidunt eget nullam non nisi est sit amet. Amet consectetur adipiscing elit ut aliquam purus sit amet luctus. Amet consectetur adipiscing elit ut aliquam purus. Nulla facilisi cras fermentum odio eu feugiat pretium nibh ipsum. Sed felis eget velit aliquet sagittis id. Felis eget nunc lobortis mattis aliquam faucibus purus in. Ornare quam viverra orci sagittis. Urna condimentum mattis pellentesque id nibh tortor id. Non consectetur a erat nam at lectus. Nisl suscipit adipiscing bibendum est ultricies integer. Urna nec tincidunt praesent semper. Venenatis urna cursus eget nunc scelerisque viverra mauris. Ut aliquam purus sit amet luctus venenatis lectus. Mattis rhoncus urna neque viverra justo nec ultrices dui sapien. Ac orci phasellus egestas tellus rutrum.

-

Leo vel orci porta non pulvinar. At auctor urna nunc id cursus metus. Massa id neque aliquam vestibulum morbi blandit. Ut eu sem integer vitae justo. Neque laoreet suspendisse interdum consectetur libero id faucibus. Duis ut diam quam nulla porttitor massa id. Justo eget magna fermentum iaculis eu non diam phasellus vestibulum. Nec nam aliquam sem et tortor consequat. Nisi scelerisque eu ultrices vitae auctor eu augue. Aliquet bibendum enim facilisis gravida neque convallis. Morbi non arcu risus quis varius quam quisque id.

-

Nulla facilisi morbi tempus iaculis urna. Elementum nisi quis eleifend quam adipiscing vitae proin sagittis nisl. Enim diam vulputate ut pharetra sit. Parturient montes nascetur ridiculus mus. Et molestie ac feugiat sed. Quis risus sed vulputate odio ut enim blandit volutpat maecenas. Imperdiet dui accumsan sit amet nulla facilisi morbi tempus. Lorem sed risus ultricies tristique nulla aliquet enim tortor. Feugiat scelerisque varius morbi enim. Ac placerat vestibulum lectus mauris. Purus in mollis nunc sed. Lorem ipsum dolor sit amet consectetur adipiscing. Arcu cursus vitae congue mauris rhoncus aenean vel.

-

Enim lobortis scelerisque fermentum dui faucibus in. Cras ornare arcu dui vivamus arcu felis bibendum ut tristique. Diam sit amet nisl suscipit. Laoreet suspendisse interdum consectetur libero id faucibus nisl tincidunt. Sed felis eget velit aliquet sagittis id consectetur purus. Id volutpat lacus laoreet non curabitur. Fermentum odio eu feugiat pretium nibh. Vestibulum lectus mauris ultrices eros in cursus turpis massa tincidunt. Et sollicitudin ac orci phasellus egestas tellus. Pellentesque habitant morbi tristique senectus et netus et. Sit amet volutpat consequat mauris. At augue eget arcu dictum varius duis at consectetur. Eget nunc lobortis mattis aliquam faucibus purus in massa. Fringilla ut morbi tincidunt augue interdum velit euismod in. Eu mi bibendum neque egestas congue. Id venenatis a condimentum vitae sapien pellentesque. Odio facilisis mauris sit amet massa vitae tortor condimentum. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam. Nascetur ridiculus mus mauris vitae ultricies leo. Felis eget nunc lobortis mattis aliquam faucibus purus in massa.

-

Tristique risus nec feugiat in fermentum. Consectetur lorem donec massa sapien faucibus et. Ac placerat vestibulum lectus mauris. Mauris pellentesque pulvinar pellentesque habitant morbi. Morbi tempus iaculis urna id volutpat lacus laoreet. Vitae semper quis lectus nulla at volutpat diam ut. Eu non diam phasellus vestibulum lorem. Vitae proin sagittis nisl rhoncus mattis rhoncus urna neque. Nascetur ridiculus mus mauris vitae ultricies leo integer. Vitae turpis massa sed elementum.

-

Quam lacus suspendisse faucibus interdum posuere lorem ipsum. Sit amet aliquam id diam maecenas ultricies mi. Praesent semper feugiat nibh sed pulvinar proin gravida. Nunc vel risus commodo viverra maecenas accumsan lacus. Eros donec ac odio tempor. Blandit turpis cursus in hac. Diam vel quam elementum pulvinar etiam non quam. Blandit volutpat maecenas volutpat blandit aliquam etiam erat velit. Volutpat commodo sed egestas egestas fringilla phasellus. Turpis egestas integer eget aliquet. Neque egestas congue quisque egestas diam in arcu cursus euismod. Tincidunt augue interdum velit euismod. Tristique et egestas quis ipsum suspendisse ultrices gravida. Quam viverra orci sagittis eu. Viverra ipsum nunc aliquet bibendum enim facilisis gravida. Consectetur lorem donec massa sapien faucibus et molestie ac feugiat. Integer quis auctor elit sed vulputate.

-

Feugiat vivamus at augue eget arcu. Turpis in eu mi bibendum neque egestas congue. Aenean pharetra magna ac placerat vestibulum lectus mauris. Est ullamcorper eget nulla facilisi etiam dignissim. Quisque non tellus orci ac auctor augue mauris augue neque. Adipiscing enim eu turpis egestas. Tristique risus nec feugiat in fermentum posuere. Donec et odio pellentesque diam volutpat commodo sed egestas. Eleifend donec pretium vulputate sapien. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Amet porttitor eget dolor morbi non arcu risus quis varius. Elit ullamcorper dignissim cras tincidunt lobortis. Habitant morbi tristique senectus et netus. Auctor elit sed vulputate mi sit amet mauris commodo. Ut sem nulla pharetra diam. Massa sapien faucibus et molestie ac feugiat sed lectus vestibulum. Quam lacus suspendisse faucibus interdum posuere lorem ipsum dolor sit. Mauris vitae ultricies leo integer malesuada.

-

Ipsum dolor sit amet consectetur adipiscing elit. Habitant morbi tristique senectus et netus. Volutpat sed cras ornare arcu dui vivamus arcu felis bibendum. Diam sollicitudin tempor id eu nisl nunc mi ipsum. Augue neque gravida in fermentum et sollicitudin ac orci phasellus. Sagittis eu volutpat odio facilisis mauris. Tortor consequat id porta nibh venenatis. Nisl condimentum id venenatis a condimentum vitae sapien pellentesque. Suspendisse interdum consectetur libero id faucibus nisl tincidunt eget nullam. Mattis molestie a iaculis at erat pellentesque adipiscing commodo. Magnis dis parturient montes nascetur ridiculus mus mauris vitae. Posuere sollicitudin aliquam ultrices sagittis.

-

Porta lorem mollis aliquam ut porttitor. Pretium lectus quam id leo in vitae turpis massa sed. Dictum sit amet justo donec enim diam vulputate ut. Felis eget velit aliquet sagittis id consectetur. Venenatis cras sed felis eget velit aliquet. Non tellus orci ac auctor augue. Id aliquet risus feugiat in ante metus dictum at tempor. Vulputate odio ut enim blandit volutpat maecenas volutpat. Urna nec tincidunt praesent semper feugiat nibh. Ac ut consequat semper viverra nam libero. Commodo quis imperdiet massa tincidunt nunc pulvinar. Purus sit amet volutpat consequat mauris nunc congue nisi. Tortor condimentum lacinia quis vel eros donec ac. Pellentesque habitant morbi tristique senectus. Ac felis donec et odio pellentesque diam volutpat. Etiam dignissim diam quis enim lobortis scelerisque fermentum dui faucibus. Quam id leo in vitae.

-

Fusce ut placerat orci nulla pellentesque dignissim enim. Mauris a diam maecenas sed. Enim praesent elementum facilisis leo vel fringilla est. Cursus metus aliquam eleifend mi in nulla posuere sollicitudin. Egestas erat imperdiet sed euismod nisi porta lorem mollis aliquam. Viverra suspendisse potenti nullam ac tortor. Tempus iaculis urna id volutpat lacus laoreet non. Sed viverra ipsum nunc aliquet. Nulla facilisi cras fermentum odio eu feugiat. Sed augue lacus viverra vitae congue. Pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus. Feugiat pretium nibh ipsum consequat nisl vel pretium. Tristique senectus et netus et malesuada fames ac. Nunc mattis enim ut tellus elementum sagittis. Quis viverra nibh cras pulvinar mattis nunc sed blandit. Sapien eget mi proin sed libero enim sed. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel. Aliquet enim tortor at auctor urna. Cursus turpis massa tincidunt dui ut ornare. Pulvinar elementum integer enim neque.

-

Urna nec tincidunt praesent semper feugiat. Ultrices gravida dictum fusce ut placerat orci nulla pellentesque dignissim. Phasellus faucibus scelerisque eleifend donec pretium vulputate sapien nec sagittis. Eget dolor morbi non arcu risus. Feugiat sed lectus vestibulum mattis ullamcorper velit sed. Urna et pharetra pharetra massa massa ultricies mi quis hendrerit. Nibh cras pulvinar mattis nunc sed blandit. A diam maecenas sed enim ut sem viverra aliquet eget. Sed augue lacus viverra vitae. Orci phasellus egestas tellus rutrum tellus pellentesque. Fames ac turpis egestas integer eget. Vel pretium lectus quam id leo in vitae turpis. Tincidunt lobortis feugiat vivamus at augue. Pellentesque habitant morbi tristique senectus. Elit eget gravida cum sociis natoque penatibus et magnis dis. Nunc mattis enim ut tellus elementum sagittis vitae. Enim praesent elementum facilisis leo vel fringilla est. Non pulvinar neque laoreet suspendisse interdum consectetur libero. Sit amet aliquam id diam maecenas ultricies mi. Arcu non odio euismod lacinia.

-

Ut morbi tincidunt augue interdum velit euismod in. Ac turpis egestas sed tempus. Non consectetur a erat nam at lectus urna duis. Massa vitae tortor condimentum lacinia. Vestibulum morbi blandit cursus risus at ultrices mi tempus imperdiet. Nisl nunc mi ipsum faucibus vitae. Ornare lectus sit amet est placerat in. Tempus egestas sed sed risus pretium quam vulputate. Odio ut enim blandit volutpat maecenas. Et netus et malesuada fames ac turpis egestas sed tempus. Massa ultricies mi quis hendrerit dolor magna eget est lorem. Ultrices vitae auctor eu augue ut lectus. At quis risus sed vulputate odio. Eget magna fermentum iaculis eu non diam. Et tortor at risus viverra. Sed sed risus pretium quam vulputate dignissim suspendisse in est. Libero volutpat sed cras ornare arcu dui vivamus. Et pharetra pharetra massa massa ultricies mi quis hendrerit dolor.

-

Lorem ipsum dolor sit amet. In nisl nisi scelerisque eu ultrices vitae auctor eu. Adipiscing diam donec adipiscing tristique risus nec. Elit eget gravida cum sociis natoque. Malesuada proin libero nunc consequat interdum varius sit amet. Nulla facilisi nullam vehicula ipsum a arcu cursus. Ultricies tristique nulla aliquet enim tortor at auctor urna. Nunc faucibus a pellentesque sit amet porttitor. Turpis in eu mi bibendum neque egestas. Rutrum quisque non tellus orci ac auctor augue mauris augue. Et molestie ac feugiat sed lectus vestibulum mattis ullamcorper velit. Nec nam aliquam sem et. Egestas erat imperdiet sed euismod. Felis bibendum ut tristique et egestas quis. Nulla pellentesque dignissim enim sit amet venenatis urna. Euismod in pellentesque massa placerat duis ultricies lacus sed. At risus viverra adipiscing at in tellus integer feugiat. Ut lectus arcu bibendum at varius vel pharetra vel turpis. Vulputate dignissim suspendisse in est ante in nibh.

-

Accumsan tortor posuere ac ut consequat semper viverra nam libero. Vitae congue eu consequat ac felis. Nullam ac tortor vitae purus faucibus ornare suspendisse sed. Convallis convallis tellus id interdum. Vel elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi. Tortor id aliquet lectus proin. Aliquam malesuada bibendum arcu vitae elementum curabitur vitae. Enim sed faucibus turpis in. Dignissim convallis aenean et tortor. Quisque sagittis purus sit amet volutpat. Facilisis volutpat est velit egestas dui. Ipsum nunc aliquet bibendum enim facilisis gravida neque convallis a.

-

Sit amet consectetur adipiscing elit ut aliquam purus. Dolor purus non enim praesent elementum facilisis leo vel fringilla. Ac tortor dignissim convallis aenean et tortor at risus viverra. Consectetur adipiscing elit duis tristique sollicitudin. Viverra justo nec ultrices dui sapien eget mi proin sed. Massa tempor nec feugiat nisl pretium. Morbi tincidunt ornare massa eget egestas purus. Est lorem ipsum dolor sit amet consectetur adipiscing elit. Natoque penatibus et magnis dis parturient montes nascetur ridiculus. Vulputate enim nulla aliquet porttitor lacus.

-

Mauris pharetra et ultrices neque ornare aenean. Ut sem nulla pharetra diam sit amet nisl. Eu sem integer vitae justo eget magna. Vitae justo eget magna fermentum iaculis. Quisque id diam vel quam elementum. Bibendum enim facilisis gravida neque convallis a. Ultricies tristique nulla aliquet enim tortor at. Id diam maecenas ultricies mi eget mauris pharetra et ultrices. Urna condimentum mattis pellentesque id nibh tortor id aliquet. Vitae tortor condimentum lacinia quis vel. Amet luctus venenatis lectus magna fringilla. Aliquam ultrices sagittis orci a scelerisque purus. Condimentum mattis pellentesque id nibh tortor. Diam volutpat commodo sed egestas egestas fringilla phasellus faucibus. Rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat. Blandit aliquam etiam erat velit. Morbi blandit cursus risus at. Massa placerat duis ultricies lacus sed turpis tincidunt id. At lectus urna duis convallis convallis.

-

Faucibus a pellentesque sit amet. Massa placerat duis ultricies lacus sed. At in tellus integer feugiat. Nibh sit amet commodo nulla facilisi nullam vehicula ipsum. At tellus at urna condimentum mattis pellentesque. Arcu bibendum at varius vel. Euismod in pellentesque massa placerat duis. Augue lacus viverra vitae congue eu consequat. Vel risus commodo viverra maecenas accumsan lacus vel. Amet facilisis magna etiam tempor orci eu lobortis elementum nibh. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Arcu ac tortor dignissim convallis aenean et tortor at risus. Massa ultricies mi quis hendrerit dolor magna. Et netus et malesuada fames ac turpis. Egestas quis ipsum suspendisse ultrices gravida dictum fusce ut placerat. Massa enim nec dui nunc mattis enim ut. Est ante in nibh mauris cursus mattis molestie a iaculis. Ut eu sem integer vitae justo eget magna fermentum iaculis. Amet venenatis urna cursus eget nunc scelerisque viverra mauris.

-

Malesuada pellentesque elit eget gravida cum sociis natoque penatibus. Proin fermentum leo vel orci. Sagittis purus sit amet volutpat consequat. Id semper risus in hendrerit gravida. Senectus et netus et malesuada fames ac turpis. Dolor purus non enim praesent. Phasellus egestas tellus rutrum tellus pellentesque eu tincidunt. Lacus suspendisse faucibus interdum posuere. Mollis aliquam ut porttitor leo. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus. Arcu non sodales neque sodales ut etiam sit amet. Id eu nisl nunc mi ipsum faucibus vitae. Nibh sed pulvinar proin gravida hendrerit. Massa tincidunt nunc pulvinar sapien et ligula ullamcorper malesuada. Hac habitasse platea dictumst vestibulum. Dictum at tempor commodo ullamcorper a lacus. Potenti nullam ac tortor vitae purus faucibus ornare suspendisse.

-

Massa enim nec dui nunc mattis enim. Viverra orci sagittis eu volutpat odio facilisis mauris sit. Egestas sed sed risus pretium quam. Tempus quam pellentesque nec nam aliquam sem et tortor consequat. Enim nulla aliquet porttitor lacus. Leo urna molestie at elementum eu facilisis. Potenti nullam ac tortor vitae purus. Tristique et egestas quis ipsum suspendisse ultrices gravida dictum fusce. Aliquam ut porttitor leo a. Nisi vitae suscipit tellus mauris a diam maecenas sed.

-

Dui sapien eget mi proin sed libero enim. Iaculis at erat pellentesque adipiscing commodo elit at. Ligula ullamcorper malesuada proin libero nunc consequat interdum. Mattis rhoncus urna neque viverra justo nec ultrices. Aenean sed adipiscing diam donec adipiscing tristique. Massa vitae tortor condimentum lacinia quis vel eros. Viverra nibh cras pulvinar mattis nunc sed blandit libero volutpat. Feugiat pretium nibh ipsum consequat nisl vel pretium lectus. Amet facilisis magna etiam tempor orci eu lobortis elementum nibh. Scelerisque viverra mauris in aliquam. Eu turpis egestas pretium aenean pharetra magna. Ullamcorper eget nulla facilisi etiam dignissim diam. Massa id neque aliquam vestibulum morbi. Non arcu risus quis varius quam quisque id diam vel. Ipsum suspendisse ultrices gravida dictum. Mauris pharetra et ultrices neque ornare aenean euismod. Convallis tellus id interdum velit. Tristique sollicitudin nibh sit amet commodo nulla facilisi nullam vehicula. Faucibus in ornare quam viverra orci sagittis eu volutpat odio.

-

Cras sed felis eget velit aliquet sagittis. Ut placerat orci nulla pellentesque dignissim enim sit. Vitae nunc sed velit dignissim sodales. Volutpat sed cras ornare arcu dui vivamus arcu felis. Pharetra sit amet aliquam id diam. Blandit volutpat maecenas volutpat blandit aliquam etiam erat velit. Dui id ornare arcu odio ut sem. Eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis orci. Sed vulputate mi sit amet mauris. Lorem dolor sed viverra ipsum nunc aliquet bibendum enim facilisis. Lobortis mattis aliquam faucibus purus in massa tempor nec feugiat. Proin nibh nisl condimentum id venenatis.

-

Purus non enim praesent elementum facilisis. Purus sit amet volutpat consequat mauris nunc. Placerat orci nulla pellentesque dignissim enim sit amet venenatis. Arcu risus quis varius quam quisque id diam vel. Nulla at volutpat diam ut venenatis tellus in metus vulputate. Vel turpis nunc eget lorem dolor sed viverra. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Ultrices vitae auctor eu augue ut lectus arcu bibendum. Augue lacus viverra vitae congue eu consequat ac felis donec. Donec ac odio tempor orci dapibus. Nisi porta lorem mollis aliquam ut porttitor leo. Sed vulputate mi sit amet mauris commodo quis imperdiet massa.

-

Consectetur adipiscing elit duis tristique sollicitudin. Nunc id cursus metus aliquam eleifend. Id velit ut tortor pretium viverra suspendisse potenti nullam. Cursus turpis massa tincidunt dui ut ornare lectus. Nulla posuere sollicitudin aliquam ultrices. Viverra accumsan in nisl nisi scelerisque. Pulvinar etiam non quam lacus suspendisse. Velit euismod in pellentesque massa placerat duis ultricies lacus. Quis auctor elit sed vulputate. Gravida cum sociis natoque penatibus et magnis.

-

Fermentum posuere urna nec tincidunt praesent semper feugiat nibh. Et leo duis ut diam quam nulla porttitor. Suspendisse interdum consectetur libero id faucibus nisl. Elit duis tristique sollicitudin nibh sit amet. Et egestas quis ipsum suspendisse ultrices gravida dictum fusce. Eu facilisis sed odio morbi. Tellus pellentesque eu tincidunt tortor. Id porta nibh venenatis cras sed felis eget velit. Massa tincidunt dui ut ornare lectus sit. Venenatis tellus in metus vulputate. Egestas quis ipsum suspendisse ultrices gravida dictum fusce ut. Aliquet lectus proin nibh nisl condimentum id venenatis a condimentum. Id ornare arcu odio ut. Donec massa sapien faucibus et. Et leo duis ut diam quam nulla porttitor massa. Id venenatis a condimentum vitae sapien pellentesque habitant morbi. Sed id semper risus in. Vitae nunc sed velit dignissim sodales ut eu. Euismod elementum nisi quis eleifend quam. Aliquet enim tortor at auctor urna nunc id cursus.

-

Elementum nibh tellus molestie nunc non blandit massa. In ornare quam viverra orci sagittis. Sapien faucibus et molestie ac feugiat sed lectus vestibulum mattis. Ut eu sem integer vitae justo eget magna fermentum iaculis. Vel eros donec ac odio tempor. Venenatis a condimentum vitae sapien pellentesque habitant morbi tristique senectus. Pellentesque pulvinar pellentesque habitant morbi tristique senectus. Cursus turpis massa tincidunt dui ut. Tristique et egestas quis ipsum suspendisse ultrices. Nunc consequat interdum varius sit amet mattis vulputate enim. Nulla malesuada pellentesque elit eget gravida cum. Eget aliquet nibh praesent tristique magna sit amet. Malesuada pellentesque elit eget gravida cum sociis natoque penatibus et. Sagittis vitae et leo duis ut diam quam nulla. Venenatis lectus magna fringilla urna porttitor. In pellentesque massa placerat duis ultricies lacus sed.

-

Odio facilisis mauris sit amet massa vitae tortor condimentum lacinia. Platea dictumst quisque sagittis purus sit amet. Pharetra convallis posuere morbi leo urna molestie at. Nullam eget felis eget nunc lobortis mattis. Cras semper auctor neque vitae tempus quam pellentesque nec nam. Porttitor rhoncus dolor purus non enim. Lorem ipsum dolor sit amet. Urna molestie at elementum eu facilisis. Magna eget est lorem ipsum dolor. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet consectetur. Ipsum faucibus vitae aliquet nec ullamcorper sit amet risus nullam. Elementum sagittis vitae et leo duis ut diam quam nulla. Dolor morbi non arcu risus quis varius quam quisque. Nunc aliquet bibendum enim facilisis gravida neque convallis a.

-

Tortor consequat id porta nibh venenatis cras sed felis. Congue eu consequat ac felis donec. Risus at ultrices mi tempus. Et magnis dis parturient montes nascetur ridiculus mus mauris vitae. Malesuada proin libero nunc consequat interdum varius sit amet mattis. Turpis tincidunt id aliquet risus feugiat in ante. Lectus proin nibh nisl condimentum id venenatis. Aliquam ultrices sagittis orci a. Netus et malesuada fames ac turpis. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Arcu ac tortor dignissim convallis aenean et tortor at. Dignissim sodales ut eu sem integer. Auctor urna nunc id cursus metus aliquam eleifend. Nulla facilisi morbi tempus iaculis urna id volutpat. Bibendum ut tristique et egestas quis ipsum suspendisse. Magna etiam tempor orci eu lobortis elementum nibh tellus molestie. Magna fermentum iaculis eu non diam phasellus vestibulum lorem.

-

Laoreet non curabitur gravida arcu. Egestas tellus rutrum tellus pellentesque. Platea dictumst vestibulum rhoncus est pellentesque elit. Praesent elementum facilisis leo vel fringilla est ullamcorper. Augue eget arcu dictum varius duis at consectetur lorem donec. Vulputate enim nulla aliquet porttitor lacus. Volutpat commodo sed egestas egestas fringilla phasellus faucibus. Non curabitur gravida arcu ac tortor dignissim convallis aenean. Amet nisl purus in mollis nunc. Feugiat in ante metus dictum at tempor commodo ullamcorper a. Mauris cursus mattis molestie a iaculis at erat. Amet porttitor eget dolor morbi non arcu risus. Velit aliquet sagittis id consectetur purus ut faucibus. Pulvinar etiam non quam lacus suspendisse faucibus interdum. Euismod nisi porta lorem mollis aliquam ut porttitor. Vulputate odio ut enim blandit volutpat maecenas. Commodo nulla facilisi nullam vehicula ipsum a. Nunc vel risus commodo viverra maecenas accumsan lacus vel facilisis.

-

Fringilla ut morbi tincidunt augue interdum velit euismod in pellentesque. Arcu risus quis varius quam quisque id. Amet est placerat in egestas erat. Cras sed felis eget velit aliquet. Netus et malesuada fames ac turpis egestas. Quis varius quam quisque id diam vel quam elementum pulvinar. Diam vel quam elementum pulvinar. Mattis rhoncus urna neque viverra justo. Tincidunt eget nullam non nisi. Velit dignissim sodales ut eu sem integer vitae. Feugiat in fermentum posuere urna nec tincidunt. Quam pellentesque nec nam aliquam sem et tortor. In nisl nisi scelerisque eu ultrices vitae auctor eu augue.

-

Commodo sed egestas egestas fringilla phasellus faucibus scelerisque. Non diam phasellus vestibulum lorem sed risus ultricies. Dignissim convallis aenean et tortor at. Purus sit amet volutpat consequat mauris nunc congue nisi. Faucibus nisl tincidunt eget nullam non nisi est sit amet. Amet consectetur adipiscing elit ut aliquam purus sit amet luctus. Amet consectetur adipiscing elit ut aliquam purus. Nulla facilisi cras fermentum odio eu feugiat pretium nibh ipsum. Sed felis eget velit aliquet sagittis id. Felis eget nunc lobortis mattis aliquam faucibus purus in. Ornare quam viverra orci sagittis. Urna condimentum mattis pellentesque id nibh tortor id. Non consectetur a erat nam at lectus. Nisl suscipit adipiscing bibendum est ultricies integer. Urna nec tincidunt praesent semper. Venenatis urna cursus eget nunc scelerisque viverra mauris. Ut aliquam purus sit amet luctus venenatis lectus. Mattis rhoncus urna neque viverra justo nec ultrices dui sapien. Ac orci phasellus egestas tellus rutrum.

-

Leo vel orci porta non pulvinar. At auctor urna nunc id cursus metus. Massa id neque aliquam vestibulum morbi blandit. Ut eu sem integer vitae justo. Neque laoreet suspendisse interdum consectetur libero id faucibus. Duis ut diam quam nulla porttitor massa id. Justo eget magna fermentum iaculis eu non diam phasellus vestibulum. Nec nam aliquam sem et tortor consequat. Nisi scelerisque eu ultrices vitae auctor eu augue. Aliquet bibendum enim facilisis gravida neque convallis. Morbi non arcu risus quis varius quam quisque id.

-

Nulla facilisi morbi tempus iaculis urna. Elementum nisi quis eleifend quam adipiscing vitae proin sagittis nisl. Enim diam vulputate ut pharetra sit. Parturient montes nascetur ridiculus mus. Et molestie ac feugiat sed. Quis risus sed vulputate odio ut enim blandit volutpat maecenas. Imperdiet dui accumsan sit amet nulla facilisi morbi tempus. Lorem sed risus ultricies tristique nulla aliquet enim tortor. Feugiat scelerisque varius morbi enim. Ac placerat vestibulum lectus mauris. Purus in mollis nunc sed. Lorem ipsum dolor sit amet consectetur adipiscing. Arcu cursus vitae congue mauris rhoncus aenean vel.

-

Enim lobortis scelerisque fermentum dui faucibus in. Cras ornare arcu dui vivamus arcu felis bibendum ut tristique. Diam sit amet nisl suscipit. Laoreet suspendisse interdum consectetur libero id faucibus nisl tincidunt. Sed felis eget velit aliquet sagittis id consectetur purus. Id volutpat lacus laoreet non curabitur. Fermentum odio eu feugiat pretium nibh. Vestibulum lectus mauris ultrices eros in cursus turpis massa tincidunt. Et sollicitudin ac orci phasellus egestas tellus. Pellentesque habitant morbi tristique senectus et netus et. Sit amet volutpat consequat mauris. At augue eget arcu dictum varius duis at consectetur. Eget nunc lobortis mattis aliquam faucibus purus in massa. Fringilla ut morbi tincidunt augue interdum velit euismod in. Eu mi bibendum neque egestas congue. Id venenatis a condimentum vitae sapien pellentesque. Odio facilisis mauris sit amet massa vitae tortor condimentum. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam. Nascetur ridiculus mus mauris vitae ultricies leo. Felis eget nunc lobortis mattis aliquam faucibus purus in massa.

-

Tristique risus nec feugiat in fermentum. Consectetur lorem donec massa sapien faucibus et. Ac placerat vestibulum lectus mauris. Mauris pellentesque pulvinar pellentesque habitant morbi. Morbi tempus iaculis urna id volutpat lacus laoreet. Vitae semper quis lectus nulla at volutpat diam ut. Eu non diam phasellus vestibulum lorem. Vitae proin sagittis nisl rhoncus mattis rhoncus urna neque. Nascetur ridiculus mus mauris vitae ultricies leo integer. Vitae turpis massa sed elementum.

- - diff --git a/src/Tools/dotnet-watch/README.md b/src/Tools/dotnet-watch/README.md deleted file mode 100644 index 2981ac22e83a..000000000000 --- a/src/Tools/dotnet-watch/README.md +++ /dev/null @@ -1,92 +0,0 @@ -dotnet-watch -============ -`dotnet-watch` is a file watcher for `dotnet` that restarts the specified application when changes in the source code are detected. - -### How To Use - -The command must be executed in the directory that contains the project to be watched. - - Usage: dotnet watch [options] [[--] ...] - - Options: - -?|-h|--help Show help information - -q|--quiet Suppresses all output except warnings and errors - -v|--verbose Show verbose output - -Add `watch` after `dotnet` and before the command arguments that you want to run: - -| What you want to run | Dotnet watch command | -| ---------------------------------------------- | -------------------------------------------------------- | -| dotnet run | dotnet **watch** run | -| dotnet run --arg1 value1 | dotnet **watch** run --arg1 value | -| dotnet run --framework net451 -- --arg1 value1 | dotnet **watch** run --framework net451 -- --arg1 value1 | -| dotnet test | dotnet **watch** test | - -### Environment variables - -Some configuration options can be passed to `dotnet watch` through environment variables. The available variables are: - -| Variable | Effect | -| ---------------------------------------------- | -------------------------------------------------------- | -| DOTNET_USE_POLLING_FILE_WATCHER | If set to "1" or "true", `dotnet watch` will use a polling file watcher instead of CoreFx's `FileSystemWatcher`. Used when watching files on network shares or Docker mounted volumes. | -| DOTNET_WATCH_SUPPRESS_MSBUILD_INCREMENTALISM | By default, `dotnet watch` optimizes the build by avoiding certain operations such as running restore or re-evaluating the set of watched files on every file change. If set to "1" or "true", these optimizations are disabled. | -| DOTNET_WATCH_SUPPRESS_LAUNCH_BROWSER | `dotnet watch run` will attempt to launch browsers for web apps with `launchBrowser` configured in `launchSettings.json`. If set to "1" or "true", this behavior is suppressed. | -| DOTNET_WATCH_SUPPRESS_MSBUILD_INCREMENTALISM | `dotnet watch run` will attempt to refresh browsers when it detects file changes. If set to "1" or "true", this behavior is suppressed. This behavior is also suppressed if DOTNET_WATCH_SUPPRESS_LAUNCH_BROWSER is set. | -| DOTNET_WATCH_SUPPRESS_STATIC_FILE_HANDLING | If set to "1", or "true", `dotnet watch` will not perform special handling for static content file - -### MSBuild - -dotnet-watch can be configured from the MSBuild project file being watched. - -**Watch items** - -dotnet-watch will watch all items in the **Watch** item group. -By default, this group inclues all items in **Compile** and **EmbeddedResource**. - -More items can be added to watch in a project file by adding items to 'Watch'. - -```xml - - - - -``` - -dotnet-watch will ignore Compile and EmbeddedResource items with the `Watch="false"` attribute. - -Example: - -```xml - - - - - - -``` - -**Project References** - -By default, dotnet-watch will scan the entire graph of project references and watch all files within those projects. - -dotnet-watch will ignore project references with the `Watch="false"` attribute. - -```xml - - - -``` - - -**Advanced configuration** - -dotnet-watch performs a design-time build to find items to watch. -When this build is run, dotnet-watch will set the property `DotNetWatchBuild=true`. - -Example: - -```xml - - - -``` diff --git a/src/Tools/dotnet-watch/src/BrowserRefreshServer.cs b/src/Tools/dotnet-watch/src/BrowserRefreshServer.cs deleted file mode 100644 index 86afe8232584..000000000000 --- a/src/Tools/dotnet-watch/src/BrowserRefreshServer.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Hosting.Server.Features; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Tools -{ - public class BrowserRefreshServer : IAsyncDisposable - { - private readonly byte[] ReloadMessage = Encoding.UTF8.GetBytes("Reload"); - private readonly byte[] WaitMessage = Encoding.UTF8.GetBytes("Wait"); - private readonly IReporter _reporter; - private readonly TaskCompletionSource _taskCompletionSource; - private IHost _refreshServer; - private WebSocket _webSocket; - - public BrowserRefreshServer(IReporter reporter) - { - _reporter = reporter; - _taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } - - public async ValueTask StartAsync(CancellationToken cancellationToken) - { - var hostName = Environment.GetEnvironmentVariable("DOTNET_WATCH_AUTO_RELOAD_WS_HOSTNAME") ?? "127.0.0.1"; - - _refreshServer = new HostBuilder() - .ConfigureWebHost(builder => - { - builder.UseKestrel(); - builder.UseUrls($"http://{hostName}:0"); - - builder.Configure(app => - { - app.UseWebSockets(); - app.Run(WebSocketRequest); - }); - }) - .Build(); - - await _refreshServer.StartAsync(cancellationToken); - - var serverUrl = _refreshServer.Services - .GetRequiredService() - .Features - .Get() - .Addresses - .First(); - - return serverUrl.Replace("http://", "ws://"); - } - - private async Task WebSocketRequest(HttpContext context) - { - if (!context.WebSockets.IsWebSocketRequest) - { - context.Response.StatusCode = 400; - return; - } - - _webSocket = await context.WebSockets.AcceptWebSocketAsync(); - await _taskCompletionSource.Task; - } - - public async virtual ValueTask SendMessage(ReadOnlyMemory messageBytes, CancellationToken cancellationToken = default) - { - if (_webSocket == null || _webSocket.CloseStatus.HasValue) - { - return; - } - - try - { - await _webSocket.SendAsync(messageBytes, WebSocketMessageType.Text, endOfMessage: true, cancellationToken); - } - catch (Exception ex) - { - _reporter.Verbose($"Refresh server error: {ex}"); - } - } - - public async ValueTask DisposeAsync() - { - if (_webSocket != null) - { - await _webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, default); - _webSocket.Dispose(); - } - - if (_refreshServer != null) - { - _refreshServer.Dispose(); - } - - _taskCompletionSource.TrySetResult(); - } - - public ValueTask ReloadAsync(CancellationToken cancellationToken) => SendMessage(ReloadMessage, cancellationToken); - - public ValueTask SendWaitMessageAsync(CancellationToken cancellationToken) => SendMessage(WaitMessage, cancellationToken); - } -} diff --git a/src/Tools/dotnet-watch/src/CommandLineOptions.cs b/src/Tools/dotnet-watch/src/CommandLineOptions.cs deleted file mode 100644 index b864785e07a7..000000000000 --- a/src/Tools/dotnet-watch/src/CommandLineOptions.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Reflection; -using Microsoft.DotNet.Watcher.Tools; -using Microsoft.Extensions.CommandLineUtils; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher -{ - internal class CommandLineOptions - { - public string Project { get; private set; } - public bool IsHelp { get; private set; } - public bool IsQuiet { get; private set; } - public bool IsVerbose { get; private set; } - public IList RemainingArguments { get; private set; } - public bool ListFiles { get; private set; } - - public static bool IsPollingEnabled - { - get - { - var envVar = Environment.GetEnvironmentVariable("DOTNET_USE_POLLING_FILE_WATCHER"); - return envVar != null && - (envVar.Equals("1", StringComparison.OrdinalIgnoreCase) || - envVar.Equals("true", StringComparison.OrdinalIgnoreCase)); - } - } - - public static CommandLineOptions Parse(string[] args, IConsole console) - { - Ensure.NotNull(args, nameof(args)); - Ensure.NotNull(console, nameof(console)); - - var app = new CommandLineApplication(throwOnUnexpectedArg: false) - { - Name = "dotnet watch", - FullName = "Microsoft DotNet File Watcher", - Out = console.Out, - Error = console.Error, - AllowArgumentSeparator = true, - ExtendedHelpText = @" -Environment variables: - - DOTNET_USE_POLLING_FILE_WATCHER - When set to '1' or 'true', dotnet-watch will poll the file system for - changes. This is required for some file systems, such as network shares, - Docker mounted volumes, and other virtual file systems. - - DOTNET_WATCH - dotnet-watch sets this variable to '1' on all child processes launched. - - DOTNET_WATCH_ITERATION - dotnet-watch sets this variable to '1' and increments by one each time - a file is changed and the command is restarted. - -Remarks: - The special option '--' is used to delimit the end of the options and - the beginning of arguments that will be passed to the child dotnet process. - Its use is optional. When the special option '--' is not used, - dotnet-watch will use the first unrecognized argument as the beginning - of all arguments passed into the child dotnet process. - - For example: dotnet watch -- --verbose run - - Even though '--verbose' is an option dotnet-watch supports, the use of '--' - indicates that '--verbose' should be treated instead as an argument for - dotnet-run. - -Examples: - dotnet watch run - dotnet watch test -" - }; - - app.HelpOption("-?|-h|--help"); - // TODO multiple shouldn't be too hard to support - var optProjects = app.Option("-p|--project ", "The project to watch", - CommandOptionType.SingleValue); - - var optQuiet = app.Option("-q|--quiet", "Suppresses all output except warnings and errors", - CommandOptionType.NoValue); - var optVerbose = app.VerboseOption(); - - var optList = app.Option("--list", "Lists all discovered files without starting the watcher", - CommandOptionType.NoValue); - - app.VersionOptionFromAssemblyAttributes(typeof(Program).Assembly); - - if (app.Execute(args) != 0) - { - return null; - } - - if (optQuiet.HasValue() && optVerbose.HasValue()) - { - throw new CommandParsingException(app, Resources.Error_QuietAndVerboseSpecified); - } - - if (app.RemainingArguments.Count == 0 - && !app.IsShowingInformation - && !optList.HasValue()) - { - app.ShowHelp(); - } - - return new CommandLineOptions - { - Project = optProjects.Value(), - IsQuiet = optQuiet.HasValue(), - IsVerbose = optVerbose.HasValue(), - RemainingArguments = app.RemainingArguments, - IsHelp = app.IsShowingInformation, - ListFiles = optList.HasValue(), - }; - } - } -} diff --git a/src/Tools/dotnet-watch/src/DotNetWatchContext.cs b/src/Tools/dotnet-watch/src/DotNetWatchContext.cs deleted file mode 100644 index 2fe09ada7d22..000000000000 --- a/src/Tools/dotnet-watch/src/DotNetWatchContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Tools -{ - public class DotNetWatchContext - { - public IReporter Reporter { get; set; } = NullReporter.Singleton; - - public ProcessSpec ProcessSpec { get; set; } - - public FileSet FileSet { get; set; } - - public int Iteration { get; set; } - - public FileItem? ChangedFile { get; set; } - - public bool RequiresMSBuildRevaluation { get; set; } - - public bool SuppressMSBuildIncrementalism { get; set; } - - public BrowserRefreshServer BrowserRefreshServer { get; set; } - } -} diff --git a/src/Tools/dotnet-watch/src/DotNetWatchOptions.cs b/src/Tools/dotnet-watch/src/DotNetWatchOptions.cs deleted file mode 100644 index 5ddda827d589..000000000000 --- a/src/Tools/dotnet-watch/src/DotNetWatchOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.DotNet.Watcher -{ - public record DotNetWatchOptions( - bool SuppressHandlingStaticContentFiles, - bool SuppressMSBuildIncrementalism) - { - public static DotNetWatchOptions Default { get; } = new DotNetWatchOptions - ( - SuppressHandlingStaticContentFiles: GetSuppressedValue("DOTNET_WATCH_SUPPRESS_STATIC_FILE_HANDLING"), - SuppressMSBuildIncrementalism: GetSuppressedValue("DOTNET_WATCH_SUPPRESS_MSBUILD_INCREMENTALISM") - ); - - private static bool GetSuppressedValue(string key) - { - var envValue = Environment.GetEnvironmentVariable(key); - return envValue == "1" || envValue == "true"; - } - } -} diff --git a/src/Tools/dotnet-watch/src/DotNetWatcher.cs b/src/Tools/dotnet-watch/src/DotNetWatcher.cs deleted file mode 100644 index 7106597d067d..000000000000 --- a/src/Tools/dotnet-watch/src/DotNetWatcher.cs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.DotNet.Watcher.Internal; -using Microsoft.DotNet.Watcher.Tools; -using Microsoft.Extensions.CommandLineUtils; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher -{ - public class DotNetWatcher : IAsyncDisposable - { - private readonly IReporter _reporter; - private readonly ProcessRunner _processRunner; - private readonly DotNetWatchOptions _dotnetWatchOptions; - private readonly IWatchFilter[] _filters; - - public DotNetWatcher(IReporter reporter, IFileSetFactory fileSetFactory, DotNetWatchOptions dotNetWatchOptions) - { - Ensure.NotNull(reporter, nameof(reporter)); - - _reporter = reporter; - _processRunner = new ProcessRunner(reporter); - _dotnetWatchOptions = dotNetWatchOptions; - - _filters = new IWatchFilter[] - { - new MSBuildEvaluationFilter(fileSetFactory), - new NoRestoreFilter(), - new LaunchBrowserFilter(), - }; - } - - public async Task WatchAsync(ProcessSpec processSpec, CancellationToken cancellationToken) - { - Ensure.NotNull(processSpec, nameof(processSpec)); - - var cancelledTaskSource = new TaskCompletionSource(); - cancellationToken.Register(state => ((TaskCompletionSource)state).TrySetResult(), - cancelledTaskSource); - - var initialArguments = processSpec.Arguments.ToArray(); - var context = new DotNetWatchContext - { - Iteration = -1, - ProcessSpec = processSpec, - Reporter = _reporter, - SuppressMSBuildIncrementalism = _dotnetWatchOptions.SuppressMSBuildIncrementalism, - }; - - if (context.SuppressMSBuildIncrementalism) - { - _reporter.Verbose("MSBuild incremental optimizations suppressed."); - } - - while (true) - { - context.Iteration++; - - // Reset arguments - processSpec.Arguments = initialArguments; - - for (var i = 0; i < _filters.Length; i++) - { - await _filters[i].ProcessAsync(context, cancellationToken); - } - - // Reset for next run - context.RequiresMSBuildRevaluation = false; - - processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture); - - var fileSet = context.FileSet; - if (fileSet == null) - { - _reporter.Error("Failed to find a list of files to watch"); - return; - } - - if (cancellationToken.IsCancellationRequested) - { - return; - } - - using (var currentRunCancellationSource = new CancellationTokenSource()) - using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource( - cancellationToken, - currentRunCancellationSource.Token)) - using (var fileSetWatcher = new FileSetWatcher(fileSet, _reporter)) - { - var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token); - var args = ArgumentEscaper.EscapeAndConcatenate(processSpec.Arguments); - _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}"); - - _reporter.Output("Started"); - - Task fileSetTask; - Task finishedTask; - - while (true) - { - fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token); - finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task); - - if (context.BrowserRefreshServer is not null && - finishedTask == fileSetTask && - fileSetTask.Result is FileItem { FileKind: FileKind.StaticFile } file) - { - _reporter.Verbose($"Handling file change event for static content {file.FilePath}."); - - // If we can handle the file change without a browser refresh, do it. - await StaticContentHandler.TryHandleFileAction(context.BrowserRefreshServer, file, combinedCancellationSource.Token); - } - else - { - break; - } - } - - // Regardless of the which task finished first, make sure everything is cancelled - // and wait for dotnet to exit. We don't want orphan processes - currentRunCancellationSource.Cancel(); - - await Task.WhenAll(processTask, fileSetTask); - - if (processTask.Result != 0 && finishedTask == processTask && !cancellationToken.IsCancellationRequested) - { - // Only show this error message if the process exited non-zero due to a normal process exit. - // Don't show this if dotnet-watch killed the inner process due to file change or CTRL+C by the user - _reporter.Error($"Exited with error code {processTask.Result}"); - } - else - { - _reporter.Output("Exited"); - } - - if (finishedTask == cancelledTaskSource.Task || cancellationToken.IsCancellationRequested) - { - return; - } - - if (finishedTask == processTask) - { - // Process exited. Redo evaludation - context.RequiresMSBuildRevaluation = true; - // Now wait for a file to change before restarting process - context.ChangedFile = await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _reporter.Warn("Waiting for a file to change before restarting dotnet...")); - } - else - { - Debug.Assert(finishedTask == fileSetTask); - var changedFile = fileSetTask.Result; - context.ChangedFile = changedFile; - _reporter.Output($"File changed: {changedFile.Value.FilePath}"); - } - } - } - } - - public async ValueTask DisposeAsync() - { - foreach (var filter in _filters) - { - if (filter is IAsyncDisposable asyncDisposable) - { - await asyncDisposable.DisposeAsync(); - } - else if (filter is IDisposable diposable) - { - diposable.Dispose(); - } - - } - } - } -} diff --git a/src/Tools/dotnet-watch/src/DotnetToolSettings.xml b/src/Tools/dotnet-watch/src/DotnetToolSettings.xml deleted file mode 100644 index f077af6da491..000000000000 --- a/src/Tools/dotnet-watch/src/DotnetToolSettings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Tools/dotnet-watch/src/FileItem.cs b/src/Tools/dotnet-watch/src/FileItem.cs deleted file mode 100644 index 560b88c66281..000000000000 --- a/src/Tools/dotnet-watch/src/FileItem.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.DotNet.Watcher -{ - public readonly struct FileItem - { - public FileItem(string filePath, FileKind fileKind = FileKind.Default, string staticWebAssetPath = null) - { - FilePath = filePath; - FileKind = fileKind; - StaticWebAssetPath = staticWebAssetPath; - } - - public string FilePath { get; } - - public FileKind FileKind { get; } - - public string StaticWebAssetPath { get; } - } -} diff --git a/src/Tools/dotnet-watch/src/FileKind.cs b/src/Tools/dotnet-watch/src/FileKind.cs deleted file mode 100644 index 2cb4d50c8829..000000000000 --- a/src/Tools/dotnet-watch/src/FileKind.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.DotNet.Watcher -{ - public enum FileKind - { - Default, - StaticFile, - } -} diff --git a/src/Tools/dotnet-watch/src/FileSet.cs b/src/Tools/dotnet-watch/src/FileSet.cs deleted file mode 100644 index 28ef122a9a13..000000000000 --- a/src/Tools/dotnet-watch/src/FileSet.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Microsoft.DotNet.Watcher -{ - public class FileSet : IEnumerable - { - private readonly Dictionary _files; - - public FileSet(bool isNetCoreApp31OrNewer, IEnumerable files) - { - IsNetCoreApp31OrNewer = isNetCoreApp31OrNewer; - _files = new Dictionary(StringComparer.Ordinal); - foreach (var item in files) - { - _files[item.FilePath] = item; - } - } - - public bool TryGetValue(string filePath, out FileItem fileItem) => _files.TryGetValue(filePath, out fileItem); - - public int Count => _files.Count; - - public bool IsNetCoreApp31OrNewer { get; } - - public static readonly FileSet Empty = new FileSet(false, Array.Empty()); - - public IEnumerator GetEnumerator() => _files.Values.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } -} diff --git a/src/Tools/dotnet-watch/src/IFileSetFactory.cs b/src/Tools/dotnet-watch/src/IFileSetFactory.cs deleted file mode 100644 index c9b832d0ad87..000000000000 --- a/src/Tools/dotnet-watch/src/IFileSetFactory.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.DotNet.Watcher.Internal; - -namespace Microsoft.DotNet.Watcher -{ - public interface IFileSetFactory - { - Task CreateAsync(CancellationToken cancellationToken); - } -} diff --git a/src/Tools/dotnet-watch/src/IWatchFilter.cs b/src/Tools/dotnet-watch/src/IWatchFilter.cs deleted file mode 100644 index 1dcfa3939238..000000000000 --- a/src/Tools/dotnet-watch/src/IWatchFilter.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.DotNet.Watcher.Tools -{ - public interface IWatchFilter - { - ValueTask ProcessAsync(DotNetWatchContext context, CancellationToken cancellationToken); - } -} diff --git a/src/Tools/dotnet-watch/src/Internal/FileSetWatcher.cs b/src/Tools/dotnet-watch/src/Internal/FileSetWatcher.cs deleted file mode 100644 index 11daee22d582..000000000000 --- a/src/Tools/dotnet-watch/src/Internal/FileSetWatcher.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Internal -{ - public class FileSetWatcher : IDisposable - { - private readonly FileWatcher _fileWatcher; - private readonly FileSet _fileSet; - - public FileSetWatcher(FileSet fileSet, IReporter reporter) - { - Ensure.NotNull(fileSet, nameof(fileSet)); - - _fileSet = fileSet; - _fileWatcher = new FileWatcher(reporter); - } - - public async Task GetChangedFileAsync(CancellationToken cancellationToken, Action startedWatching) - { - foreach (var file in _fileSet) - { - _fileWatcher.WatchDirectory(Path.GetDirectoryName(file.FilePath)); - } - - var tcs = new TaskCompletionSource(); - cancellationToken.Register(() => tcs.TrySetResult(null)); - - void callback(string path) - { - if (_fileSet.TryGetValue(path, out var fileItem)) - { - tcs.TrySetResult(fileItem); - } - } - - _fileWatcher.OnFileChange += callback; - startedWatching(); - var changedFile = await tcs.Task; - _fileWatcher.OnFileChange -= callback; - - return changedFile; - } - - public Task GetChangedFileAsync(CancellationToken cancellationToken) - { - return GetChangedFileAsync(cancellationToken, () => {}); - } - - public void Dispose() - { - _fileWatcher.Dispose(); - } - } -} diff --git a/src/Tools/dotnet-watch/src/Internal/FileWatcher.cs b/src/Tools/dotnet-watch/src/Internal/FileWatcher.cs deleted file mode 100644 index 216537b15a1a..000000000000 --- a/src/Tools/dotnet-watch/src/Internal/FileWatcher.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Internal -{ - public class FileWatcher - { - private bool _disposed; - - private readonly IDictionary _watchers; - private readonly IReporter _reporter; - - public FileWatcher() - : this(NullReporter.Singleton) - { } - - public FileWatcher(IReporter reporter) - { - _reporter = reporter ?? throw new ArgumentNullException(nameof(reporter)); - _watchers = new Dictionary(); - } - - public event Action OnFileChange; - - public void WatchDirectory(string directory) - { - EnsureNotDisposed(); - AddDirectoryWatcher(directory); - } - - public void Dispose() - { - if (_disposed) - { - return; - } - - _disposed = true; - - foreach (var watcher in _watchers) - { - watcher.Value.OnFileChange -= WatcherChangedHandler; - watcher.Value.OnError -= WatcherErrorHandler; - watcher.Value.Dispose(); - } - - _watchers.Clear(); - } - - private void AddDirectoryWatcher(string directory) - { - directory = EnsureTrailingSlash(directory); - - var alreadyWatched = _watchers - .Where(d => directory.StartsWith(d.Key, StringComparison.Ordinal)) - .Any(); - - if (alreadyWatched) - { - return; - } - - var redundantWatchers = _watchers - .Where(d => d.Key.StartsWith(directory, StringComparison.Ordinal)) - .Select(d => d.Key) - .ToList(); - - if (redundantWatchers.Any()) - { - foreach (var watcher in redundantWatchers) - { - DisposeWatcher(watcher); - } - } - - var newWatcher = FileWatcherFactory.CreateWatcher(directory); - newWatcher.OnFileChange += WatcherChangedHandler; - newWatcher.OnError += WatcherErrorHandler; - newWatcher.EnableRaisingEvents = true; - - _watchers.Add(directory, newWatcher); - } - - private void WatcherErrorHandler(object sender, Exception error) - { - if (sender is IFileSystemWatcher watcher) - { - _reporter.Warn($"The file watcher observing '{watcher.BasePath}' encountered an error: {error.Message}"); - } - } - - private void WatcherChangedHandler(object sender, string changedPath) - { - NotifyChange(changedPath); - } - - private void NotifyChange(string path) - { - if (OnFileChange != null) - { - OnFileChange(path); - } - } - - private void DisposeWatcher(string directory) - { - var watcher = _watchers[directory]; - _watchers.Remove(directory); - - watcher.EnableRaisingEvents = false; - - watcher.OnFileChange -= WatcherChangedHandler; - watcher.OnError -= WatcherErrorHandler; - - watcher.Dispose(); - } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(FileWatcher)); - } - } - - private static string EnsureTrailingSlash(string path) - { - if (!string.IsNullOrEmpty(path) && - path[path.Length - 1] != Path.DirectorySeparatorChar) - { - return path + Path.DirectorySeparatorChar; - } - - return path; - } - } -} \ No newline at end of file diff --git a/src/Tools/dotnet-watch/src/Internal/FileWatcher/DotnetFileWatcher.cs b/src/Tools/dotnet-watch/src/Internal/FileWatcher/DotnetFileWatcher.cs deleted file mode 100644 index 0372327819e2..000000000000 --- a/src/Tools/dotnet-watch/src/Internal/FileWatcher/DotnetFileWatcher.cs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.ComponentModel; -using System.IO; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Internal -{ - internal class DotnetFileWatcher : IFileSystemWatcher - { - private volatile bool _disposed; - - private readonly Func _watcherFactory; - - private FileSystemWatcher _fileSystemWatcher; - - private readonly object _createLock = new object(); - - public DotnetFileWatcher(string watchedDirectory) - : this(watchedDirectory, DefaultWatcherFactory) - { - } - - internal DotnetFileWatcher(string watchedDirectory, Func fileSystemWatcherFactory) - { - Ensure.NotNull(fileSystemWatcherFactory, nameof(fileSystemWatcherFactory)); - Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory)); - - BasePath = watchedDirectory; - _watcherFactory = fileSystemWatcherFactory; - CreateFileSystemWatcher(); - } - - public event EventHandler OnFileChange; - - public event EventHandler OnError; - - public string BasePath { get; } - - private static FileSystemWatcher DefaultWatcherFactory(string watchedDirectory) - { - Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory)); - - return new FileSystemWatcher(watchedDirectory); - } - - private void WatcherErrorHandler(object sender, ErrorEventArgs e) - { - if (_disposed) - { - return; - } - - var exception = e.GetException(); - - // Win32Exception may be triggered when setting EnableRaisingEvents on a file system type - // that is not supported, such as a network share. Don't attempt to recreate the watcher - // in this case as it will cause a StackOverflowException - if (!(exception is Win32Exception)) - { - // Recreate the watcher if it is a recoverable error. - CreateFileSystemWatcher(); - } - - OnError?.Invoke(this, exception); - } - - private void WatcherRenameHandler(object sender, RenamedEventArgs e) - { - if (_disposed) - { - return; - } - - NotifyChange(e.OldFullPath); - NotifyChange(e.FullPath); - - if (Directory.Exists(e.FullPath)) - { - foreach (var newLocation in Directory.EnumerateFileSystemEntries(e.FullPath, "*", SearchOption.AllDirectories)) - { - // Calculated previous path of this moved item. - var oldLocation = Path.Combine(e.OldFullPath, newLocation.Substring(e.FullPath.Length + 1)); - NotifyChange(oldLocation); - NotifyChange(newLocation); - } - } - } - - private void WatcherChangeHandler(object sender, FileSystemEventArgs e) - { - if (_disposed) - { - return; - } - - NotifyChange(e.FullPath); - } - - private void NotifyChange(string fullPath) - { - // Only report file changes - OnFileChange?.Invoke(this, fullPath); - } - - private void CreateFileSystemWatcher() - { - lock (_createLock) - { - bool enableEvents = false; - - if (_fileSystemWatcher != null) - { - enableEvents = _fileSystemWatcher.EnableRaisingEvents; - - DisposeInnerWatcher(); - } - - _fileSystemWatcher = _watcherFactory(BasePath); - _fileSystemWatcher.IncludeSubdirectories = true; - - _fileSystemWatcher.Created += WatcherChangeHandler; - _fileSystemWatcher.Deleted += WatcherChangeHandler; - _fileSystemWatcher.Changed += WatcherChangeHandler; - _fileSystemWatcher.Renamed += WatcherRenameHandler; - _fileSystemWatcher.Error += WatcherErrorHandler; - - _fileSystemWatcher.EnableRaisingEvents = enableEvents; - } - } - - private void DisposeInnerWatcher() - { - _fileSystemWatcher.EnableRaisingEvents = false; - - _fileSystemWatcher.Created -= WatcherChangeHandler; - _fileSystemWatcher.Deleted -= WatcherChangeHandler; - _fileSystemWatcher.Changed -= WatcherChangeHandler; - _fileSystemWatcher.Renamed -= WatcherRenameHandler; - _fileSystemWatcher.Error -= WatcherErrorHandler; - - _fileSystemWatcher.Dispose(); - } - - public bool EnableRaisingEvents - { - get => _fileSystemWatcher.EnableRaisingEvents; - set => _fileSystemWatcher.EnableRaisingEvents = value; - } - - public void Dispose() - { - _disposed = true; - DisposeInnerWatcher(); - } - } -} diff --git a/src/Tools/dotnet-watch/src/Internal/FileWatcher/FileWatcherFactory.cs b/src/Tools/dotnet-watch/src/Internal/FileWatcher/FileWatcherFactory.cs deleted file mode 100644 index 9c91176ec123..000000000000 --- a/src/Tools/dotnet-watch/src/Internal/FileWatcher/FileWatcherFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.DotNet.Watcher.Internal -{ - public static class FileWatcherFactory - { - public static IFileSystemWatcher CreateWatcher(string watchedDirectory) - => CreateWatcher(watchedDirectory, CommandLineOptions.IsPollingEnabled); - - public static IFileSystemWatcher CreateWatcher(string watchedDirectory, bool usePollingWatcher) - { - return usePollingWatcher ? - new PollingFileWatcher(watchedDirectory) : - new DotnetFileWatcher(watchedDirectory) as IFileSystemWatcher; - } - } -} diff --git a/src/Tools/dotnet-watch/src/Internal/FileWatcher/IFileSystemWatcher.cs b/src/Tools/dotnet-watch/src/Internal/FileWatcher/IFileSystemWatcher.cs deleted file mode 100644 index aaf57734495b..000000000000 --- a/src/Tools/dotnet-watch/src/Internal/FileWatcher/IFileSystemWatcher.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.DotNet.Watcher.Internal -{ - public interface IFileSystemWatcher : IDisposable - { - event EventHandler OnFileChange; - - event EventHandler OnError; - - string BasePath { get; } - - bool EnableRaisingEvents { get; set; } - } -} diff --git a/src/Tools/dotnet-watch/src/Internal/FileWatcher/PollingFileWatcher.cs b/src/Tools/dotnet-watch/src/Internal/FileWatcher/PollingFileWatcher.cs deleted file mode 100644 index 023b75a1b734..000000000000 --- a/src/Tools/dotnet-watch/src/Internal/FileWatcher/PollingFileWatcher.cs +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Internal -{ - internal class PollingFileWatcher : IFileSystemWatcher - { - // The minimum interval to rerun the scan - private static readonly TimeSpan _minRunInternal = TimeSpan.FromSeconds(.5); - - private readonly DirectoryInfo _watchedDirectory; - - private Dictionary _knownEntities = new Dictionary(); - private Dictionary _tempDictionary = new Dictionary(); - private HashSet _changes = new HashSet(); - - private Thread _pollingThread; - private bool _raiseEvents; - - private bool _disposed; - - public PollingFileWatcher(string watchedDirectory) - { - Ensure.NotNullOrEmpty(watchedDirectory, nameof(watchedDirectory)); - - _watchedDirectory = new DirectoryInfo(watchedDirectory); - BasePath = _watchedDirectory.FullName; - - _pollingThread = new Thread(new ThreadStart(PollingLoop)); - _pollingThread.IsBackground = true; - _pollingThread.Name = nameof(PollingFileWatcher); - - CreateKnownFilesSnapshot(); - - _pollingThread.Start(); - } - - public event EventHandler OnFileChange; - -#pragma warning disable CS0067 // not used - public event EventHandler OnError; -#pragma warning restore - - public string BasePath { get; } - - public bool EnableRaisingEvents - { - get => _raiseEvents; - set - { - EnsureNotDisposed(); - _raiseEvents = value; - } - } - - private void PollingLoop() - { - var stopwatch = Stopwatch.StartNew(); - stopwatch.Start(); - - while (!_disposed) - { - if (stopwatch.Elapsed < _minRunInternal) - { - // Don't run too often - // The min wait time here can be double - // the value of the variable (FYI) - Thread.Sleep(_minRunInternal); - } - - stopwatch.Reset(); - - if (!_raiseEvents) - { - continue; - } - - CheckForChangedFiles(); - } - - stopwatch.Stop(); - } - - private void CreateKnownFilesSnapshot() - { - _knownEntities.Clear(); - - ForeachEntityInDirectory(_watchedDirectory, f => - { - _knownEntities.Add(f.FullName, new FileMeta(f)); - }); - } - - private void CheckForChangedFiles() - { - _changes.Clear(); - - ForeachEntityInDirectory(_watchedDirectory, f => - { - var fullFilePath = f.FullName; - - if (!_knownEntities.ContainsKey(fullFilePath)) - { - // New file - RecordChange(f); - } - else - { - var fileMeta = _knownEntities[fullFilePath]; - - try - { - if (fileMeta.FileInfo.LastWriteTime != f.LastWriteTime) - { - // File changed - RecordChange(f); - } - - _knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, true); - } - catch (FileNotFoundException) - { - _knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, false); - } - } - - _tempDictionary.Add(f.FullName, new FileMeta(f)); - }); - - foreach (var file in _knownEntities) - { - if (!file.Value.FoundAgain) - { - // File deleted - RecordChange(file.Value.FileInfo); - } - } - - NotifyChanges(); - - // Swap the two dictionaries - var swap = _knownEntities; - _knownEntities = _tempDictionary; - _tempDictionary = swap; - - _tempDictionary.Clear(); - } - - private void RecordChange(FileSystemInfo fileInfo) - { - if (fileInfo == null || - _changes.Contains(fileInfo.FullName) || - fileInfo.FullName.Equals(_watchedDirectory.FullName, StringComparison.Ordinal)) - { - return; - } - - _changes.Add(fileInfo.FullName); - if (fileInfo.FullName != _watchedDirectory.FullName) - { - var file = fileInfo as FileInfo; - if (file != null) - { - RecordChange(file.Directory); - } - else - { - var dir = fileInfo as DirectoryInfo; - if (dir != null) - { - RecordChange(dir.Parent); - } - } - } - } - - private void ForeachEntityInDirectory(DirectoryInfo dirInfo, Action fileAction) - { - if (!dirInfo.Exists) - { - return; - } - - IEnumerable entities; - try - { - entities = dirInfo.EnumerateFileSystemInfos("*.*"); - } - // If the directory is deleted after the exists check this will throw and could crash the process - catch (DirectoryNotFoundException) - { - return; - } - - foreach (var entity in entities) - { - fileAction(entity); - - var subdirInfo = entity as DirectoryInfo; - if (subdirInfo != null) - { - ForeachEntityInDirectory(subdirInfo, fileAction); - } - } - } - - private void NotifyChanges() - { - foreach (var path in _changes) - { - if (_disposed || !_raiseEvents) - { - break; - } - - if (OnFileChange != null) - { - OnFileChange(this, path); - } - } - } - - private void EnsureNotDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(PollingFileWatcher)); - } - } - - public void Dispose() - { - EnableRaisingEvents = false; - _disposed = true; - } - - private struct FileMeta - { - public FileMeta(FileSystemInfo fileInfo, bool foundAgain = false) - { - FileInfo = fileInfo; - FoundAgain = foundAgain; - } - - public FileSystemInfo FileInfo; - - public bool FoundAgain; - } - } -} diff --git a/src/Tools/dotnet-watch/src/Internal/MsBuildFileSetFactory.cs b/src/Tools/dotnet-watch/src/Internal/MsBuildFileSetFactory.cs deleted file mode 100644 index ecd0734b88d0..000000000000 --- a/src/Tools/dotnet-watch/src/Internal/MsBuildFileSetFactory.cs +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.CommandLineUtils; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Internal -{ - public class MsBuildFileSetFactory : IFileSetFactory - { - private const string TargetName = "GenerateWatchList"; - private const string WatchTargetsFileName = "DotNetWatch.targets"; - private readonly IReporter _reporter; - private readonly DotNetWatchOptions _dotNetWatchOptions; - private readonly string _projectFile; - private readonly OutputSink _outputSink; - private readonly ProcessRunner _processRunner; - private readonly bool _waitOnError; - private readonly IReadOnlyList _buildFlags; - - public MsBuildFileSetFactory( - IReporter reporter, - DotNetWatchOptions dotNetWatchOptions, - string projectFile, - bool waitOnError, - bool trace) - : this(reporter, dotNetWatchOptions, projectFile, new OutputSink(), trace) - { - _waitOnError = waitOnError; - } - - // output sink is for testing - internal MsBuildFileSetFactory(IReporter reporter, - DotNetWatchOptions dotNetWatchOptions, - string projectFile, - OutputSink outputSink, - bool trace) - { - Ensure.NotNull(reporter, nameof(reporter)); - Ensure.NotNullOrEmpty(projectFile, nameof(projectFile)); - Ensure.NotNull(outputSink, nameof(outputSink)); - - _reporter = reporter; - _dotNetWatchOptions = dotNetWatchOptions; - _projectFile = projectFile; - _outputSink = outputSink; - _processRunner = new ProcessRunner(reporter); - _buildFlags = InitializeArgs(FindTargetsFile(), trace); - } - - public async Task CreateAsync(CancellationToken cancellationToken) - { - var watchList = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - try - { - var projectDir = Path.GetDirectoryName(_projectFile); - - while (true) - { - cancellationToken.ThrowIfCancellationRequested(); - - var capture = _outputSink.StartCapture(); - // TODO adding files doesn't currently work. Need to provide a way to detect new files - // find files - var processSpec = new ProcessSpec - { - Executable = DotNetMuxer.MuxerPathOrDefault(), - WorkingDirectory = projectDir, - Arguments = new[] - { - "msbuild", - "/nologo", - _projectFile, - $"/p:_DotNetWatchListFile={watchList}", - _dotNetWatchOptions.SuppressHandlingStaticContentFiles ? "/p:DotNetWatchContentFiles=false" : "", - }.Concat(_buildFlags), - OutputCapture = capture - }; - - _reporter.Verbose($"Running MSBuild target '{TargetName}' on '{_projectFile}'"); - - var exitCode = await _processRunner.RunAsync(processSpec, cancellationToken); - - if (exitCode == 0 && File.Exists(watchList)) - { - var lines = File.ReadAllLines(watchList); - - var staticFiles = new List(); - var commandLine = new CommandLineApplication(); - var contentFiles = commandLine.Option("-c", "Content file", CommandOptionType.MultipleValue); - var contentFilePaths = commandLine.Option("-s", "Static asset path", CommandOptionType.MultipleValue); - var files = commandLine.Option("-f", "Watched files", CommandOptionType.MultipleValue); - var isNetCoreApp31 = commandLine.Option("-isnetcoreapp31", "Is .NET Core 3.1 or newer?", CommandOptionType.NoValue); - commandLine.Invoke = () => 0; - - commandLine.Execute(lines); - var isNetCoreApp31OrNewer = isNetCoreApp31.Value(); - var fileItems = new List(); - foreach (var file in files.Values) - { - fileItems.Add(new FileItem(file)); - } - - for (var i = 0; i < contentFiles.Values.Count; i++) - { - var contentFile = contentFiles.Values[i]; - var staticWebAssetPath = contentFilePaths.Values[i].TrimStart('/'); - - fileItems.Add(new FileItem(contentFile, FileKind.StaticFile, staticWebAssetPath)); - } - - var fileset = new FileSet(isNetCoreApp31.HasValue(), fileItems); - - _reporter.Verbose($"Watching {fileset.Count} file(s) for changes"); -#if DEBUG - - foreach (var file in fileset) - { - _reporter.Verbose($" -> {file.FilePath} {file.FileKind} {file.StaticWebAssetPath}."); - } - - Debug.Assert(fileset.All(f => Path.IsPathRooted(f.FilePath)), "All files should be rooted paths"); -#endif - - return fileset; - } - - _reporter.Error($"Error(s) finding watch items project file '{Path.GetFileName(_projectFile)}'"); - - _reporter.Output($"MSBuild output from target '{TargetName}':"); - _reporter.Output(string.Empty); - - foreach (var line in capture.Lines) - { - _reporter.Output($" {line}"); - } - - _reporter.Output(string.Empty); - - if (!_waitOnError) - { - return null; - } - else - { - _reporter.Warn("Fix the error to continue or press Ctrl+C to exit."); - - var fileSet = new FileSet(false, new[] { new FileItem(_projectFile) }); - - using (var watcher = new FileSetWatcher(fileSet, _reporter)) - { - await watcher.GetChangedFileAsync(cancellationToken); - - _reporter.Output($"File changed: {_projectFile}"); - } - } - } - } - finally - { - if (File.Exists(watchList)) - { - File.Delete(watchList); - } - } - } - - private IReadOnlyList InitializeArgs(string watchTargetsFile, bool trace) - { - var args = new List - { - "/nologo", - "/v:n", - "/t:" + TargetName, - "/p:DotNetWatchBuild=true", // extensibility point for users - "/p:DesignTimeBuild=true", // don't do expensive things - "/p:CustomAfterMicrosoftCommonTargets=" + watchTargetsFile, - "/p:CustomAfterMicrosoftCommonCrossTargetingTargets=" + watchTargetsFile, - }; - - if (trace) - { - // enables capturing markers to know which projects have been visited - args.Add("/p:_DotNetWatchTraceOutput=true"); - } - - return args; - } - - private string FindTargetsFile() - { - var assemblyDir = Path.GetDirectoryName(typeof(MsBuildFileSetFactory).Assembly.Location); - var searchPaths = new[] - { - Path.Combine(AppContext.BaseDirectory, "assets"), - Path.Combine(assemblyDir, "assets"), - AppContext.BaseDirectory, - assemblyDir, - }; - - var targetPath = searchPaths.Select(p => Path.Combine(p, WatchTargetsFileName)).FirstOrDefault(File.Exists); - if (targetPath == null) - { - _reporter.Error("Fatal error: could not find DotNetWatch.targets"); - return null; - } - return targetPath; - } - } -} diff --git a/src/Tools/dotnet-watch/src/Internal/MsBuildProjectFinder.cs b/src/Tools/dotnet-watch/src/Internal/MsBuildProjectFinder.cs deleted file mode 100644 index b6cc515aecc5..000000000000 --- a/src/Tools/dotnet-watch/src/Internal/MsBuildProjectFinder.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Linq; -using Microsoft.DotNet.Watcher.Tools; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Internal -{ - internal class MsBuildProjectFinder - { - /// - /// Finds a compatible MSBuild project. - /// The base directory to search - /// The filename of the project. Can be null. - /// - public static string FindMsBuildProject(string searchBase, string project) - { - Ensure.NotNullOrEmpty(searchBase, nameof(searchBase)); - - var projectPath = project ?? searchBase; - - if (!Path.IsPathRooted(projectPath)) - { - projectPath = Path.Combine(searchBase, projectPath); - } - - if (Directory.Exists(projectPath)) - { - var projects = Directory.EnumerateFileSystemEntries(projectPath, "*.*proj", SearchOption.TopDirectoryOnly) - .Where(f => !".xproj".Equals(Path.GetExtension(f), StringComparison.OrdinalIgnoreCase)) - .ToList(); - - if (projects.Count > 1) - { - throw new FileNotFoundException(Resources.FormatError_MultipleProjectsFound(projectPath)); - } - - if (projects.Count == 0) - { - throw new FileNotFoundException(Resources.FormatError_NoProjectsFound(projectPath)); - } - - return projects[0]; - } - - if (!File.Exists(projectPath)) - { - throw new FileNotFoundException(Resources.FormatError_ProjectPath_NotFound(projectPath)); - } - - return projectPath; - } - } -} \ No newline at end of file diff --git a/src/Tools/dotnet-watch/src/Internal/OutputCapture.cs b/src/Tools/dotnet-watch/src/Internal/OutputCapture.cs deleted file mode 100644 index 08e051f732c0..000000000000 --- a/src/Tools/dotnet-watch/src/Internal/OutputCapture.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.DotNet.Watcher.Internal -{ - public class OutputCapture - { - private readonly List _lines = new List(); - public IEnumerable Lines => _lines; - public void AddLine(string line) => _lines.Add(line); - } -} \ No newline at end of file diff --git a/src/Tools/dotnet-watch/src/Internal/OutputSink.cs b/src/Tools/dotnet-watch/src/Internal/OutputSink.cs deleted file mode 100644 index eb176564eff9..000000000000 --- a/src/Tools/dotnet-watch/src/Internal/OutputSink.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.DotNet.Watcher.Internal -{ - public class OutputSink - { - public OutputCapture Current { get; private set; } - public OutputCapture StartCapture() - { - return (Current = new OutputCapture()); - } - } -} \ No newline at end of file diff --git a/src/Tools/dotnet-watch/src/Internal/ProcessRunner.cs b/src/Tools/dotnet-watch/src/Internal/ProcessRunner.cs deleted file mode 100644 index 050828467ab0..000000000000 --- a/src/Tools/dotnet-watch/src/Internal/ProcessRunner.cs +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.CommandLineUtils; -using Microsoft.Extensions.Internal; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Internal -{ - public class ProcessRunner - { - private readonly IReporter _reporter; - - public ProcessRunner(IReporter reporter) - { - Ensure.NotNull(reporter, nameof(reporter)); - - _reporter = reporter; - } - - // May not be necessary in the future. See https://github.com/dotnet/corefx/issues/12039 - public async Task RunAsync(ProcessSpec processSpec, CancellationToken cancellationToken) - { - Ensure.NotNull(processSpec, nameof(processSpec)); - - int exitCode; - - var stopwatch = new Stopwatch(); - - using (var process = CreateProcess(processSpec)) - using (var processState = new ProcessState(process, _reporter)) - { - cancellationToken.Register(() => processState.TryKill()); - - var readOutput = false; - var readError = false; - if (processSpec.IsOutputCaptured) - { - readOutput = true; - readError = true; - process.OutputDataReceived += (_, a) => - { - if (!string.IsNullOrEmpty(a.Data)) - { - processSpec.OutputCapture.AddLine(a.Data); - } - }; - process.ErrorDataReceived += (_, a) => - { - if (!string.IsNullOrEmpty(a.Data)) - { - processSpec.OutputCapture.AddLine(a.Data); - } - }; - } - else if (processSpec.OnOutput != null) - { - readOutput = true; - process.OutputDataReceived += processSpec.OnOutput; - } - - stopwatch.Start(); - process.Start(); - - _reporter.Verbose($"Started '{processSpec.Executable}' with process id {process.Id}"); - - if (readOutput) - { - process.BeginOutputReadLine(); - } - if (readError) - { - process.BeginErrorReadLine(); - } - - await processState.Task; - - exitCode = process.ExitCode; - stopwatch.Stop(); - _reporter.Verbose($"Process id {process.Id} ran for {stopwatch.ElapsedMilliseconds}ms"); - } - - return exitCode; - } - - private Process CreateProcess(ProcessSpec processSpec) - { - var process = new Process - { - EnableRaisingEvents = true, - StartInfo = - { - FileName = processSpec.Executable, - Arguments = ArgumentEscaper.EscapeAndConcatenate(processSpec.Arguments), - UseShellExecute = false, - WorkingDirectory = processSpec.WorkingDirectory, - RedirectStandardOutput = processSpec.IsOutputCaptured || (processSpec.OnOutput != null), - RedirectStandardError = processSpec.IsOutputCaptured, - } - }; - - foreach (var env in processSpec.EnvironmentVariables) - { - process.StartInfo.Environment.Add(env.Key, env.Value); - } - - return process; - } - - private class ProcessState : IDisposable - { - private readonly IReporter _reporter; - private readonly Process _process; - private readonly TaskCompletionSource _tcs = new TaskCompletionSource(); - private volatile bool _disposed; - - public ProcessState(Process process, IReporter reporter) - { - _reporter = reporter; - _process = process; - _process.Exited += OnExited; - Task = _tcs.Task.ContinueWith(_ => - { - try - { - // We need to use two WaitForExit calls to ensure that all of the output/events are processed. Previously - // this code used Process.Exited, which could result in us missing some output due to the ordering of - // events. - // - // See the remarks here: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexit#System_Diagnostics_Process_WaitForExit_System_Int32_ - if (!_process.WaitForExit(Int32.MaxValue)) - { - throw new TimeoutException(); - } - - _process.WaitForExit(); - } - catch (InvalidOperationException) - { - // suppress if this throws if no process is associated with this object anymore. - } - }); - } - - public Task Task { get; } - - public void TryKill() - { - if (_disposed) - { - return; - } - - try - { - if (!_process.HasExited) - { - _reporter.Verbose($"Killing process {_process.Id}"); - _process.KillTree(); - } - } - catch (Exception ex) - { - _reporter.Verbose($"Error while killing process '{_process.StartInfo.FileName} {_process.StartInfo.Arguments}': {ex.Message}"); -#if DEBUG - _reporter.Verbose(ex.ToString()); -#endif - } - } - - private void OnExited(object sender, EventArgs args) - => _tcs.TrySetResult(null); - - public void Dispose() - { - if (!_disposed) - { - TryKill(); - _disposed = true; - _process.Exited -= OnExited; - _process.Dispose(); - } - } - } - } -} diff --git a/src/Tools/dotnet-watch/src/LaunchBrowserFilter.cs b/src/Tools/dotnet-watch/src/LaunchBrowserFilter.cs deleted file mode 100644 index 784037b7319c..000000000000 --- a/src/Tools/dotnet-watch/src/LaunchBrowserFilter.cs +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.ComponentModel.Design; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.Json; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Tools -{ - public sealed class LaunchBrowserFilter : IWatchFilter, IAsyncDisposable - { - private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: (?.*)$", RegexOptions.None | RegexOptions.Compiled, TimeSpan.FromSeconds(10)); - private readonly bool _runningInTest; - private readonly bool _suppressLaunchBrowser; - private readonly bool _suppressBrowserRefresh; - private readonly string _browserPath; - - private bool _canLaunchBrowser; - private Process _browserProcess; - private bool _browserLaunched; - private BrowserRefreshServer _refreshServer; - private IReporter _reporter; - private string _launchPath; - private CancellationToken _cancellationToken; - - public LaunchBrowserFilter() - { - var suppressLaunchBrowser = Environment.GetEnvironmentVariable("DOTNET_WATCH_SUPPRESS_LAUNCH_BROWSER"); - _suppressLaunchBrowser = (suppressLaunchBrowser == "1" || suppressLaunchBrowser == "true"); - - var suppressBrowserRefresh = Environment.GetEnvironmentVariable("DOTNET_WATCH_SUPPRESS_BROWSER_REFRESH"); - _suppressBrowserRefresh = (suppressBrowserRefresh == "1" || suppressBrowserRefresh == "true"); - - _runningInTest = Environment.GetEnvironmentVariable("__DOTNET_WATCH_RUNNING_AS_TEST") == "true"; - _browserPath = Environment.GetEnvironmentVariable("DOTNET_WATCH_BROWSER_PATH"); - } - - public async ValueTask ProcessAsync(DotNetWatchContext context, CancellationToken cancellationToken) - { - if (_suppressLaunchBrowser) - { - return; - } - - if (context.Iteration == 0) - { - _reporter = context.Reporter; - - if (CanLaunchBrowser(context, out var launchPath)) - { - context.Reporter.Verbose("dotnet-watch is configured to launch a browser on ASP.NET Core application startup."); - _canLaunchBrowser = true; - _launchPath = launchPath; - _cancellationToken = cancellationToken; - - // We've redirected the output, but want to ensure that it continues to appear in the user's console. - context.ProcessSpec.OnOutput += (_, eventArgs) => Console.WriteLine(eventArgs.Data); - context.ProcessSpec.OnOutput += OnOutput; - - if (!_suppressBrowserRefresh) - { - _refreshServer = new BrowserRefreshServer(context.Reporter); - var serverUrl = await _refreshServer.StartAsync(cancellationToken); - - context.BrowserRefreshServer = _refreshServer; - - context.Reporter.Verbose($"Refresh server running at {serverUrl}."); - context.ProcessSpec.EnvironmentVariables["ASPNETCORE_AUTO_RELOAD_WS_ENDPOINT"] = serverUrl; - - var pathToMiddleware = Path.Combine(AppContext.BaseDirectory, "middleware", "Microsoft.AspNetCore.Watch.BrowserRefresh.dll"); - context.ProcessSpec.EnvironmentVariables["DOTNET_STARTUP_HOOKS"] = pathToMiddleware; - context.ProcessSpec.EnvironmentVariables["ASPNETCORE_HOSTINGSTARTUPASSEMBLIES"] = "Microsoft.AspNetCore.Watch.BrowserRefresh"; - } - } - } - - if (_canLaunchBrowser) - { - if (context.Iteration > 0) - { - // We've detected a change. Notify the browser. - await (_refreshServer?.SendWaitMessageAsync(cancellationToken) ?? default); - } - } - } - - private void OnOutput(object sender, DataReceivedEventArgs eventArgs) - { - if (string.IsNullOrEmpty(eventArgs.Data)) - { - return; - } - - var match = NowListeningRegex.Match(eventArgs.Data); - if (match.Success) - { - var launchUrl = match.Groups["url"].Value; - - var process = (Process)sender; - process.OutputDataReceived -= OnOutput; - - if (!_browserLaunched) - { - _reporter.Verbose("Launching browser."); - try - { - LaunchBrowser(launchUrl); - _browserLaunched = true; - } - catch (Exception ex) - { - _reporter.Output($"Unable to launch browser: {ex}"); - _canLaunchBrowser = false; - } - } - else - { - _reporter.Verbose("Reloading browser."); - _ = _refreshServer?.ReloadAsync(_cancellationToken); - } - } - } - - private void LaunchBrowser(string launchUrl) - { - var fileName = launchUrl + "/" + _launchPath; - var args = string.Empty; - if (!string.IsNullOrEmpty(_browserPath)) - { - args = fileName; - fileName = _browserPath; - } - - if (_runningInTest) - { - _reporter.Output($"Launching browser: {fileName} {args}"); - return; - } - - _browserProcess = Process.Start(new ProcessStartInfo - { - FileName = fileName, - Arguments = args, - UseShellExecute = true, - }); - } - - private static bool CanLaunchBrowser(DotNetWatchContext context, out string launchUrl) - { - launchUrl = null; - var reporter = context.Reporter; - - if (!context.FileSet.IsNetCoreApp31OrNewer) - { - // Browser refresh middleware supports 3.1 or newer - reporter.Verbose("Browser refresh is only supported in .NET Core 3.1 or newer projects."); - return false; - } - - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - // Launching a browser requires file associations that are not available in all operating systems. - reporter.Verbose("Browser refresh is only supported in Windows and MacOS."); - return false; - } - - var dotnetCommand = context.ProcessSpec.Arguments.FirstOrDefault(); - if (!string.Equals(dotnetCommand, "run", StringComparison.Ordinal)) - { - reporter.Verbose("Browser refresh is only supported for run commands."); - return false; - } - - // We're executing the run-command. Determine if the launchSettings allows it - var launchSettingsPath = Path.Combine(context.ProcessSpec.WorkingDirectory, "Properties", "launchSettings.json"); - if (!File.Exists(launchSettingsPath)) - { - reporter.Verbose($"No launchSettings.json file found at {launchSettingsPath}. Unable to determine if browser refresh is allowed."); - return false; - } - - LaunchSettingsJson launchSettings; - try - { - launchSettings = JsonSerializer.Deserialize( - File.ReadAllText(launchSettingsPath), - new JsonSerializerOptions(JsonSerializerDefaults.Web)); - } - catch (Exception ex) - { - reporter.Verbose($"Error reading launchSettings.json: {ex}."); - return false; - } - - var defaultProfile = launchSettings.Profiles.FirstOrDefault(f => f.Value.CommandName == "Project").Value; - if (defaultProfile is null) - { - reporter.Verbose("Unable to find default launchSettings profile."); - return false; - } - - if (!defaultProfile.LaunchBrowser) - { - reporter.Verbose("launchSettings does not allow launching browsers."); - return false; - } - - launchUrl = defaultProfile.LaunchUrl; - return true; - } - - public async ValueTask DisposeAsync() - { - _browserProcess?.Dispose(); - if (_refreshServer != null) - { - await _refreshServer.DisposeAsync(); - } - } - } -} diff --git a/src/Tools/dotnet-watch/src/LaunchSettingsJson.cs b/src/Tools/dotnet-watch/src/LaunchSettingsJson.cs deleted file mode 100644 index dc739b6c45b3..000000000000 --- a/src/Tools/dotnet-watch/src/LaunchSettingsJson.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.DotNet.Watcher.Tools -{ - public class LaunchSettingsJson - { - public Dictionary Profiles { get; set; } - } - - public class LaunchSettingsProfile - { - public string CommandName { get; set; } - - public bool LaunchBrowser { get; set; } - - public string LaunchUrl { get; set; } - } -} diff --git a/src/Tools/dotnet-watch/src/MSBuildEvaluationFilter.cs b/src/Tools/dotnet-watch/src/MSBuildEvaluationFilter.cs deleted file mode 100644 index 46d3c025fec4..000000000000 --- a/src/Tools/dotnet-watch/src/MSBuildEvaluationFilter.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.DotNet.Watcher.Tools -{ - public class MSBuildEvaluationFilter : IWatchFilter - { - // File types that require an MSBuild re-evaluation - private static readonly string[] _msBuildFileExtensions = new[] - { - ".csproj", ".props", ".targets", ".fsproj", ".vbproj", ".vcxproj", - }; - private static readonly int[] _msBuildFileExtensionHashes = _msBuildFileExtensions - .Select(e => e.GetHashCode(StringComparison.OrdinalIgnoreCase)) - .ToArray(); - - private readonly IFileSetFactory _factory; - - private List<(string fileName, DateTime lastWriteTimeUtc)> _msbuildFileTimestamps; - - public MSBuildEvaluationFilter(IFileSetFactory factory) - { - _factory = factory; - } - - public async ValueTask ProcessAsync(DotNetWatchContext context, CancellationToken cancellationToken) - { - if (context.SuppressMSBuildIncrementalism) - { - context.RequiresMSBuildRevaluation = true; - context.FileSet = await _factory.CreateAsync(cancellationToken); - return; - } - - if (context.Iteration == 0 || RequiresMSBuildRevaluation(context)) - { - context.RequiresMSBuildRevaluation = true; - } - - if (context.RequiresMSBuildRevaluation) - { - context.Reporter.Verbose("Evaluating dotnet-watch file set."); - - context.FileSet = await _factory.CreateAsync(cancellationToken); - _msbuildFileTimestamps = GetMSBuildFileTimeStamps(context); - } - } - - private bool RequiresMSBuildRevaluation(DotNetWatchContext context) - { - var changedFile = context.ChangedFile; - if (changedFile != null && IsMsBuildFileExtension(changedFile.Value.FilePath)) - { - return true; - } - - // The filewatcher may miss changes to files. For msbuild files, we can verify that they haven't been modified - // since the previous iteration. - // We do not have a way to identify renames or new additions that the file watcher did not pick up, - // without performing an evaluation. We will start off by keeping it simple and comparing the timestamps - // of known MSBuild files from previous run. This should cover the vast majority of cases. - - foreach (var (file, lastWriteTimeUtc) in _msbuildFileTimestamps) - { - if (GetLastWriteTimeUtcSafely(file) != lastWriteTimeUtc) - { - context.Reporter.Verbose($"Re-evaluation needed due to changes in {file}."); - - return true; - } - } - - return false; - } - - private List<(string fileName, DateTime lastModifiedUtc)> GetMSBuildFileTimeStamps(DotNetWatchContext context) - { - var msbuildFiles = new List<(string fileName, DateTime lastModifiedUtc)>(); - foreach (var file in context.FileSet) - { - if (!string.IsNullOrEmpty(file.FilePath) && IsMsBuildFileExtension(file.FilePath)) - { - msbuildFiles.Add((file.FilePath, GetLastWriteTimeUtcSafely(file.FilePath))); - } - } - - return msbuildFiles; - } - - protected virtual DateTime GetLastWriteTimeUtcSafely(string file) - { - try - { - return File.GetLastWriteTimeUtc(file); - } - catch - { - return DateTime.UtcNow; - } - } - - static bool IsMsBuildFileExtension(string fileName) - { - var extension = Path.GetExtension(fileName.AsSpan()); - var hashCode = string.GetHashCode(extension, StringComparison.OrdinalIgnoreCase); - for (var i = 0; i < _msBuildFileExtensionHashes.Length; i++) - { - if (_msBuildFileExtensionHashes[i] == hashCode && extension.Equals(_msBuildFileExtensions[i], StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - } -} diff --git a/src/Tools/dotnet-watch/src/NoRestoreFilter.cs b/src/Tools/dotnet-watch/src/NoRestoreFilter.cs deleted file mode 100644 index c33a117bcd9a..000000000000 --- a/src/Tools/dotnet-watch/src/NoRestoreFilter.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Tools -{ - public sealed class NoRestoreFilter : IWatchFilter - { - private bool _canUseNoRestore; - private string[] _noRestoreArguments; - - public ValueTask ProcessAsync(DotNetWatchContext context, CancellationToken cancellationToken) - { - if (context.SuppressMSBuildIncrementalism) - { - return default; - } - - if (context.Iteration == 0) - { - var arguments = context.ProcessSpec.Arguments; - _canUseNoRestore = CanUseNoRestore(arguments, context.Reporter); - if (_canUseNoRestore) - { - // Create run --no-restore - _noRestoreArguments = arguments.Take(1).Append("--no-restore").Concat(arguments.Skip(1)).ToArray(); - context.Reporter.Verbose($"No restore arguments: {string.Join(" ", _noRestoreArguments)}"); - } - } - else if (_canUseNoRestore) - { - if (context.RequiresMSBuildRevaluation) - { - context.Reporter.Verbose("Cannot use --no-restore since msbuild project files have changed."); - } - else - { - context.Reporter.Verbose("Modifying command to use --no-restore"); - context.ProcessSpec.Arguments = _noRestoreArguments; - } - } - - return default; - } - - private static bool CanUseNoRestore(IEnumerable arguments, IReporter reporter) - { - // For some well-known dotnet commands, we can pass in the --no-restore switch to avoid unnecessary restores between iterations. - // For now we'll support the "run" and "test" commands. - if (arguments.Any(a => string.Equals(a, "--no-restore", StringComparison.Ordinal))) - { - // Did the user already configure a --no-restore? - return false; - } - - var dotnetCommand = arguments.FirstOrDefault(); - if (string.Equals(dotnetCommand, "run", StringComparison.Ordinal) || string.Equals(dotnetCommand, "test", StringComparison.Ordinal)) - { - reporter.Verbose("Watch command can be configured to use --no-restore."); - return true; - } - else - { - reporter.Verbose($"Watch command will not use --no-restore. Unsupport dotnet-command '{dotnetCommand}'."); - return false; - } - } - } -} diff --git a/src/Tools/dotnet-watch/src/PrefixConsoleReporter.cs b/src/Tools/dotnet-watch/src/PrefixConsoleReporter.cs deleted file mode 100644 index cf13cd0733d4..000000000000 --- a/src/Tools/dotnet-watch/src/PrefixConsoleReporter.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher -{ - public class PrefixConsoleReporter : ConsoleReporter - { - private object _lock = new object(); - - private readonly string _prefix; - - public PrefixConsoleReporter(string prefix, IConsole console, bool verbose, bool quiet) - : base(console, verbose, quiet) - { - _prefix = prefix; - } - - protected override void WriteLine(TextWriter writer, string message, ConsoleColor? color) - { - lock (_lock) - { - Console.ForegroundColor = ConsoleColor.DarkGray; - writer.Write(_prefix); - Console.ResetColor(); - - base.WriteLine(writer, message, color); - } - } - } -} diff --git a/src/Tools/dotnet-watch/src/ProcessSpec.cs b/src/Tools/dotnet-watch/src/ProcessSpec.cs deleted file mode 100644 index 5e80a3f54bb3..000000000000 --- a/src/Tools/dotnet-watch/src/ProcessSpec.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading; -using Microsoft.DotNet.Watcher.Internal; - -namespace Microsoft.DotNet.Watcher -{ - public class ProcessSpec - { - public string Executable { get; set; } - public string WorkingDirectory { get; set; } - public IDictionary EnvironmentVariables { get; } = new Dictionary(); - public IEnumerable Arguments { get; set; } - public OutputCapture OutputCapture { get; set; } - - public string ShortDisplayName() - => Path.GetFileNameWithoutExtension(Executable); - - public bool IsOutputCaptured => OutputCapture != null; - - public DataReceivedEventHandler OnOutput { get; set; } - - public CancellationToken CancelOutputCapture { get; set; } - } -} diff --git a/src/Tools/dotnet-watch/src/Program.cs b/src/Tools/dotnet-watch/src/Program.cs deleted file mode 100644 index 5523561a1c42..000000000000 --- a/src/Tools/dotnet-watch/src/Program.cs +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.DotNet.Watcher.Internal; -using Microsoft.DotNet.Watcher.Tools; -using Microsoft.Extensions.CommandLineUtils; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher -{ - public class Program : IDisposable - { - private readonly IConsole _console; - private readonly string _workingDirectory; - private readonly CancellationTokenSource _cts; - private IReporter _reporter; - - public Program(IConsole console, string workingDirectory) - { - Ensure.NotNull(console, nameof(console)); - Ensure.NotNullOrEmpty(workingDirectory, nameof(workingDirectory)); - - _console = console; - _workingDirectory = workingDirectory; - _cts = new CancellationTokenSource(); - _console.CancelKeyPress += OnCancelKeyPress; - _reporter = CreateReporter(verbose: true, quiet: false, console: _console); - } - - public static async Task Main(string[] args) - { - try - { - DebugHelper.HandleDebugSwitch(ref args); - using (var program = new Program(PhysicalConsole.Singleton, Directory.GetCurrentDirectory())) - { - //if no command argument provided, we fall back to dotnet watch run - if (args.Length == 0) - { - args = new[] { "run" }; - } - return await program.RunAsync(args); - } - } - catch (Exception ex) - { - Console.Error.WriteLine("Unexpected error:"); - Console.Error.WriteLine(ex.ToString()); - return 1; - } - } - - public async Task RunAsync(string[] args) - { - CommandLineOptions options; - try - { - options = CommandLineOptions.Parse(args, _console); - } - catch (CommandParsingException ex) - { - _reporter.Error(ex.Message); - return 1; - } - - if (options == null) - { - // invalid args syntax - return 1; - } - - if (options.IsHelp) - { - return 2; - } - - // update reporter as configured by options - _reporter = CreateReporter(options.IsVerbose, options.IsQuiet, _console); - - try - { - if (_cts.IsCancellationRequested) - { - return 1; - } - - if (options.ListFiles) - { - return await ListFilesAsync(_reporter, - options.Project, - _cts.Token); - } - else - { - return await MainInternalAsync(_reporter, - options.Project, - options.RemainingArguments, - _cts.Token); - } - } - catch (Exception ex) - { - if (ex is TaskCanceledException || ex is OperationCanceledException) - { - // swallow when only exception is the CTRL+C forced an exit - return 0; - } - - _reporter.Error(ex.ToString()); - _reporter.Error("An unexpected error occurred"); - return 1; - } - } - - private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) - { - // suppress CTRL+C on the first press - args.Cancel = !_cts.IsCancellationRequested; - - if (args.Cancel) - { - _reporter.Output("Shutdown requested. Press Ctrl+C again to force exit."); - } - - _cts.Cancel(); - } - - private async Task MainInternalAsync( - IReporter reporter, - string project, - ICollection args, - CancellationToken cancellationToken) - { - // TODO multiple projects should be easy enough to add here - string projectFile; - try - { - projectFile = MsBuildProjectFinder.FindMsBuildProject(_workingDirectory, project); - } - catch (FileNotFoundException ex) - { - reporter.Error(ex.Message); - return 1; - } - - var watchOptions = DotNetWatchOptions.Default; - - var fileSetFactory = new MsBuildFileSetFactory(reporter, - watchOptions, - projectFile, - waitOnError: true, - trace: false); - var processInfo = new ProcessSpec - { - Executable = DotNetMuxer.MuxerPathOrDefault(), - WorkingDirectory = Path.GetDirectoryName(projectFile), - Arguments = args, - EnvironmentVariables = - { - ["DOTNET_WATCH"] = "1" - }, - }; - - if (CommandLineOptions.IsPollingEnabled) - { - _reporter.Output("Polling file watcher is enabled"); - } - - await using var watcher = new DotNetWatcher(reporter, fileSetFactory, watchOptions); - await watcher.WatchAsync(processInfo, cancellationToken); - - return 0; - } - - private async Task ListFilesAsync( - IReporter reporter, - string project, - CancellationToken cancellationToken) - { - // TODO multiple projects should be easy enough to add here - string projectFile; - try - { - projectFile = MsBuildProjectFinder.FindMsBuildProject(_workingDirectory, project); - } - catch (FileNotFoundException ex) - { - reporter.Error(ex.Message); - return 1; - } - - var fileSetFactory = new MsBuildFileSetFactory(reporter, - DotNetWatchOptions.Default, - projectFile, - waitOnError: false, - trace: false); - var files = await fileSetFactory.CreateAsync(cancellationToken); - - if (files == null) - { - return 1; - } - - foreach (var group in files.GroupBy(g => g.FileKind).OrderBy(g => g.Key)) - { - if (group.Key == FileKind.StaticFile) - { - _console.Out.WriteLine("::: Watch Action: Refresh browser :::"); - } - - foreach (var file in group) - { - if (file.FileKind == FileKind.StaticFile) - { - _console.Out.WriteLine($"{file.FilePath} ~/{file.StaticWebAssetPath}"); - } - else - { - _console.Out.WriteLine(file.FilePath); - } - } - } - - return 0; - } - - private static IReporter CreateReporter(bool verbose, bool quiet, IConsole console) - => new PrefixConsoleReporter("watch : ", console, verbose || CliContext.IsGlobalVerbose(), quiet); - - public void Dispose() - { - _console.CancelKeyPress -= OnCancelKeyPress; - _cts.Dispose(); - } - } -} diff --git a/src/Tools/dotnet-watch/src/Properties/AssemblyInfo.cs b/src/Tools/dotnet-watch/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 70797aee7c71..000000000000 --- a/src/Tools/dotnet-watch/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.DotNet.Watcher.Tools.Tests, PublicKey = 0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Tools/dotnet-watch/src/Resources.resx b/src/Tools/dotnet-watch/src/Resources.resx deleted file mode 100644 index b66821626b28..000000000000 --- a/src/Tools/dotnet-watch/src/Resources.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The project file '{path}' does not exist. - - - Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option. - - - Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option. - - - Cannot specify both '--quiet' and '--verbose' options. - - \ No newline at end of file diff --git a/src/Tools/dotnet-watch/src/StaticContentHandler.cs b/src/Tools/dotnet-watch/src/StaticContentHandler.cs deleted file mode 100644 index c642711ae82c..000000000000 --- a/src/Tools/dotnet-watch/src/StaticContentHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.DotNet.Watcher.Tools -{ - public class StaticContentHandler - { - private static readonly byte[] UpdateCssMessage = Encoding.UTF8.GetBytes("UpdateStaticFile||"); - private static readonly int UpdateCssMessageLength = UpdateCssMessage.Length; - - internal static async ValueTask TryHandleFileAction(BrowserRefreshServer browserRefreshServer, FileItem fileItem, CancellationToken cancellationToken) - { - var filePath = fileItem.StaticWebAssetPath; - var bytesToRent = UpdateCssMessageLength + Encoding.UTF8.GetMaxByteCount(filePath.Length); - var bytes = ArrayPool.Shared.Rent(bytesToRent); - - try - { - UpdateCssMessage.CopyTo(bytes.AsSpan()); - var length = UpdateCssMessageLength; - length += Encoding.UTF8.GetBytes(filePath, bytes.AsSpan(length)); - - await browserRefreshServer.SendMessage(bytes.AsMemory(0, length), cancellationToken); - } - finally - { - ArrayPool.Shared.Return(bytes); - } - } - } -} diff --git a/src/Tools/dotnet-watch/src/assets/DotNetWatch.targets b/src/Tools/dotnet-watch/src/assets/DotNetWatch.targets deleted file mode 100644 index 2d7816946a57..000000000000 --- a/src/Tools/dotnet-watch/src/assets/DotNetWatch.targets +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - <_IsMicrosoftNETCoreApp31OrNewer Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND - $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '3.1'))">true - - - - <_WatchListLine Include="-isnetcoreapp31" Condition="'$(_IsMicrosoftNETCoreApp31OrNewer)' == 'true'"/> - <_WatchListLine Include="-f:%(Watch.FullPath)" Condition="'%(Watch.WatchAction)'==''" /> - <_WatchListLine Include="-c:%(Watch.FullPath)" Condition="'%(Watch.WatchAction)'!=''" /> - <_WatchListLine Include="-s:%(Watch.StaticWebAssetPath)" Condition="'%(Watch.WatchAction)'!=''" /> - - - - - - - - <_CollectWatchItemsDependsOn Condition=" '$(TargetFrameworks)' != '' AND '$(TargetFramework)' == '' "> - _CollectWatchItemsPerFramework; - - <_CollectWatchItemsDependsOn Condition=" '$(TargetFramework)' != '' "> - _CoreCollectWatchItems; - $(CustomCollectWatchItems); - - - - - - - - <_TargetFramework Include="$(TargetFrameworks)" /> - - - - - - - - - - - - - - - <_DotNetWatchStaticWebAssetBasePath Condition="'$(StaticWebAssetBasePath)' != ''">$(StaticWebAssetBasePath)/ - <_DotNetWatchStaticWebAssetBasePath Condition="'$(StaticWebAssetBasePath)' == ''">_content/$(PackageId)/ - - - - - - - - - - <_WatchProjects Include="%(ProjectReference.Identity)" Condition="'%(ProjectReference.Watch)' != 'false'" /> - - - - - - - - diff --git a/src/Tools/dotnet-watch/src/dotnet-watch.csproj b/src/Tools/dotnet-watch/src/dotnet-watch.csproj deleted file mode 100644 index ac93b1171a7d..000000000000 --- a/src/Tools/dotnet-watch/src/dotnet-watch.csproj +++ /dev/null @@ -1,56 +0,0 @@ - - - - $(DefaultNetCoreTargetFramework) - exe - Command line tool to watch for source file changes during development and restart the dotnet command. - Microsoft.DotNet.Watcher.Tools - dotnet;watch - true - - false - false - - - - - - - - - - - - - - - - - - - $(ArtifactsBinDir)Microsoft.AspNetCore.Watch.BrowserRefresh\$(Configuration)\netcoreapp3.1\Microsoft.AspNetCore.Watch.BrowserRefresh.dll - - - - - - - - - - - - - - - - diff --git a/src/Tools/dotnet-watch/src/runtimeconfig.template.json b/src/Tools/dotnet-watch/src/runtimeconfig.template.json deleted file mode 100644 index f022b7ffce12..000000000000 --- a/src/Tools/dotnet-watch/src/runtimeconfig.template.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rollForwardOnNoCandidateFx": 2 -} \ No newline at end of file diff --git a/src/Tools/dotnet-watch/test/AppWithDepsTests.cs b/src/Tools/dotnet-watch/test/AppWithDepsTests.cs deleted file mode 100644 index 907d55158835..000000000000 --- a/src/Tools/dotnet-watch/test/AppWithDepsTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests -{ - public class AppWithDepsTests : IDisposable - { - private readonly AppWithDeps _app; - - public AppWithDepsTests(ITestOutputHelper logger) - { - _app = new AppWithDeps(logger); - } - - [ConditionalFact] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23994")] - [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Windows.10.Arm64;Windows.10.Arm64.Open;Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;Windows.10.Arm64v8;Windows.10.Arm64v8.Open")] - public async Task ChangeFileInDependency() - { - await _app.StartWatcherAsync(); - - var fileToChange = Path.Combine(_app.DependencyFolder, "Foo.cs"); - var programCs = File.ReadAllText(fileToChange); - File.WriteAllText(fileToChange, programCs); - - await _app.HasRestarted(); - } - - public void Dispose() - { - _app.Dispose(); - } - - private class AppWithDeps : WatchableApp - { - private const string Dependency = "Dependency"; - - public AppWithDeps(ITestOutputHelper logger) - : base("AppWithDeps", logger) - { - Scenario.AddTestProjectFolder(Dependency); - - DependencyFolder = Path.Combine(Scenario.WorkFolder, Dependency); - } - - public string DependencyFolder { get; private set; } - } - } -} diff --git a/src/Tools/dotnet-watch/test/AssertEx.cs b/src/Tools/dotnet-watch/test/AssertEx.cs deleted file mode 100644 index ea0814c6b12e..000000000000 --- a/src/Tools/dotnet-watch/test/AssertEx.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Xunit.Sdk; - -namespace Microsoft.DotNet.Watcher.Tools.Tests -{ - public static class AssertEx - { - public static void EqualFileList(string root, IEnumerable expectedFiles, FileSet actualFiles) - => EqualFileList(root, expectedFiles, actualFiles.Select(f => f.FilePath)); - - public static void EqualFileList(string root, IEnumerable expectedFiles, IEnumerable actualFiles) - { - var expected = expectedFiles.Select(p => Path.Combine(root, p)); - EqualFileList(expected, actualFiles); - } - - public static void EqualFileList(IEnumerable expectedFiles, IEnumerable actualFiles) - { - string normalize(string p) => p.Replace('\\', '/'); - var expected = new HashSet(expectedFiles.Select(normalize)); - var actual = new HashSet(actualFiles.Where(p => !string.IsNullOrEmpty(p)).Select(normalize)); - if (!expected.SetEquals(actual)) - { - throw new AssertActualExpectedException( - expected: "\n" + string.Join("\n", expected), - actual: "\n" + string.Join("\n", actual), - userMessage: "File sets should be equal"); - } - } - - public static void EqualFileList(FileSet expectedFiles, FileSet actualFiles) - { - if (expectedFiles.Count != actualFiles.Count) - { - throw new AssertCollectionCountException(expectedFiles.Count, actualFiles.Count); - } - - foreach (var expected in expectedFiles) - { - var actual = actualFiles.FirstOrDefault(f => Normalize(expected.FilePath) == Normalize(f.FilePath)); - - if (actual.FilePath is null) - { - throw new AssertActualExpectedException( - expected: $"Expected to find {expected.FilePath}.", - actual: "\n" + string.Join("\n", actualFiles.Select(f => f.FilePath)), - userMessage: "File sets should be equal."); - } - - if (expected.FileKind != actual.FileKind || expected.StaticWebAssetPath != actual.StaticWebAssetPath) - { - throw new AssertActualExpectedException( - expected: $"FileKind: {expected.FileKind} StaticWebAssetPath {expected.StaticWebAssetPath}", - actual: $"FileKind: {actual.FileKind} StaticWebAssetPath {actual.StaticWebAssetPath}", - userMessage: "Flle sets should be equal."); - } - } - - static string Normalize(string file) => file.Replace('\\', '/'); - } - } -} diff --git a/src/Tools/dotnet-watch/test/AwaitableProcess.cs b/src/Tools/dotnet-watch/test/AwaitableProcess.cs deleted file mode 100644 index f5e0bb73bef1..000000000000 --- a/src/Tools/dotnet-watch/test/AwaitableProcess.cs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; -using Microsoft.Extensions.Internal; -using Microsoft.Extensions.CommandLineUtils; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests -{ - public class AwaitableProcess : IDisposable - { - private readonly object _testOutputLock = new object(); - - private Process? _process; - private readonly ProcessSpec _spec; - private readonly List _lines; - private BufferBlock _source; - private ITestOutputHelper _logger; - private TaskCompletionSource _exited; - private bool _started; - private bool _disposed; - - public AwaitableProcess(ProcessSpec spec, ITestOutputHelper logger) - { - _spec = spec; - _logger = logger; - _source = new BufferBlock(); - _lines = new List(); - _exited = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } - - public IEnumerable Output => _lines; - - public Task Exited => _exited.Task; - - public int Id => _process?.Id ?? throw new InvalidOperationException("Start() must be called."); - - public void Start() - { - if (_process != null) - { - throw new InvalidOperationException("Already started"); - } - - _process = new Process - { - EnableRaisingEvents = true, - StartInfo = new ProcessStartInfo - { - UseShellExecute = false, - FileName = _spec.Executable, - WorkingDirectory = _spec.WorkingDirectory, - Arguments = ArgumentEscaper.EscapeAndConcatenate(_spec.Arguments), - RedirectStandardOutput = true, - RedirectStandardError = true, - Environment = - { - ["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "true" - } - } - }; - - foreach (var env in _spec.EnvironmentVariables) - { - _process.StartInfo.EnvironmentVariables[env.Key] = env.Value; - } - - _process.OutputDataReceived += OnData; - _process.ErrorDataReceived += OnData; - _process.Exited += OnExit; - - WriteTestOutput($"{DateTime.Now}: starting process: '{_process.StartInfo.FileName} {_process.StartInfo.Arguments}'"); - _process.Start(); - _started = true; - _process.BeginErrorReadLine(); - _process.BeginOutputReadLine(); - WriteTestOutput($"{DateTime.Now}: process started: '{_process.StartInfo.FileName} {_process.StartInfo.Arguments}'"); - } - - public async Task GetOutputLineAsync(string message, TimeSpan timeout) - { - WriteTestOutput($"Waiting for output line [msg == '{message}']. Will wait for {timeout.TotalSeconds} sec."); - var cts = new CancellationTokenSource(); - cts.CancelAfter(timeout); - return await GetOutputLineAsync($"[msg == '{message}']", m => string.Equals(m, message, StringComparison.Ordinal), cts.Token) - ?? throw new InvalidOperationException($"Did not find '{message} in output. {Environment.NewLine}{string.Join(Environment.NewLine, _lines)}"); - } - - public async Task GetOutputLineStartsWithAsync(string message, TimeSpan timeout) - { - WriteTestOutput($"Waiting for output line [msg.StartsWith('{message}')]. Will wait for {timeout.TotalSeconds} sec."); - var cts = new CancellationTokenSource(); - cts.CancelAfter(timeout); - return await GetOutputLineAsync($"[msg.StartsWith('{message}')]", m => m != null && m.StartsWith(message, StringComparison.Ordinal), cts.Token) - ?? throw new InvalidOperationException($"Did not find '{message} in output. {Environment.NewLine}{string.Join(Environment.NewLine, _lines)}"); - } - - private async Task GetOutputLineAsync(string predicateName, Predicate predicate, CancellationToken cancellationToken) - { - while (!_source.Completion.IsCompleted) - { - while (await _source.OutputAvailableAsync(cancellationToken)) - { - var next = await _source.ReceiveAsync(cancellationToken); - _lines.Add(next); - var match = predicate(next); - WriteTestOutput($"{DateTime.Now}: recv: '{next}'. {(match ? "Matches" : "Does not match")} condition '{predicateName}'."); - if (match) - { - return next; - } - } - } - - return null; - } - - public async Task> GetAllOutputLinesAsync(CancellationToken cancellationToken) - { - var lines = new List(); - while (!_source.Completion.IsCompleted) - { - while (await _source.OutputAvailableAsync(cancellationToken)) - { - var next = await _source.ReceiveAsync(cancellationToken); - WriteTestOutput($"{DateTime.Now}: recv: '{next}'"); - lines.Add(next); - } - } - return lines; - } - - private void OnData(object sender, DataReceivedEventArgs args) - { - var line = args.Data ?? string.Empty; - - WriteTestOutput($"{DateTime.Now}: post: '{line}'"); - _source.Post(line); - } - - private void WriteTestOutput(string text) - { - lock (_testOutputLock) - { - if (!_disposed) - { - _logger.WriteLine(text); - } - } - } - - private void OnExit(object? sender, EventArgs args) - { - // Wait to ensure the process has exited and all output consumed - Debug.Assert(_process != null); - _process.WaitForExit(); - _source.Complete(); - _exited.TrySetResult(_process.ExitCode); - WriteTestOutput($"Process {_process.Id} has exited"); - } - - public void Dispose() - { - _source.Complete(); - - lock (_testOutputLock) - { - _disposed = true; - } - - if (_process != null) - { - if (_started && !_process.HasExited) - { - _process.KillTree(); - } - - _process.CancelErrorRead(); - _process.CancelOutputRead(); - - _process.ErrorDataReceived -= OnData; - _process.OutputDataReceived -= OnData; - _process.Exited -= OnExit; - _process.Dispose(); - } - } - } -} diff --git a/src/Tools/dotnet-watch/test/BrowserLaunchTests.cs b/src/Tools/dotnet-watch/test/BrowserLaunchTests.cs deleted file mode 100644 index dfcd14e8ad3b..000000000000 --- a/src/Tools/dotnet-watch/test/BrowserLaunchTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests -{ - public class BrowserLaunchTests : IDisposable - { - private readonly WatchableApp _app; - - public BrowserLaunchTests(ITestOutputHelper logger) - { - _app = new WatchableApp("AppWithLaunchSettings", logger); - } - - [ConditionalFact(Skip = "https://github.com/dotnet/aspnetcore/issues/28479")] - [OSSkipCondition(OperatingSystems.Linux)] - public async Task LaunchesBrowserOnStart() - { - var expected = "watch : Launching browser: https://localhost:5001/"; - _app.DotnetWatchArgs.Add("--verbose"); - - await _app.StartWatcherAsync(); - - // Verify we launched the browser. - await _app.Process.GetOutputLineStartsWithAsync(expected, TimeSpan.FromMinutes(2)); - } - - [ConditionalFact(Skip = "https://github.com/dotnet/aspnetcore/issues/28479")] - [OSSkipCondition(OperatingSystems.Linux)] - public async Task RefreshesBrowserOnChange() - { - var launchBrowserMessage = "watch : Launching browser: https://localhost:5001/"; - var refreshBrowserMessage = "watch : Reloading browser"; - _app.DotnetWatchArgs.Add("--verbose"); - var source = Path.Combine(_app.SourceDirectory, "Program.cs"); - - await _app.StartWatcherAsync(); - - // Verify we launched the browser. - await _app.Process.GetOutputLineStartsWithAsync(launchBrowserMessage, TimeSpan.FromMinutes(2)); - - // Make a file change and verify we reloaded the browser. - File.SetLastWriteTime(source, DateTime.Now); - await _app.Process.GetOutputLineStartsWithAsync(refreshBrowserMessage, TimeSpan.FromMinutes(2)); - } - - [ConditionalFact(Skip = "https://github.com/dotnet/aspnetcore/issues/28479")] - [OSSkipCondition(OperatingSystems.Linux)] - public async Task UsesBrowserSpecifiedInEnvironment() - { - var launchBrowserMessage = "watch : Launching browser: mycustombrowser.bat https://localhost:5001/"; - _app.EnvironmentVariables.Add("DOTNET_WATCH_BROWSER_PATH", "mycustombrowser.bat"); - - _app.DotnetWatchArgs.Add("--verbose"); - - await _app.StartWatcherAsync(); - - // Verify we launched the browser. - await _app.Process.GetOutputLineStartsWithAsync(launchBrowserMessage, TimeSpan.FromMinutes(2)); - } - - public void Dispose() - { - _app.Dispose(); - } - } -} diff --git a/src/Tools/dotnet-watch/test/CommandLineOptionsTests.cs b/src/Tools/dotnet-watch/test/CommandLineOptionsTests.cs deleted file mode 100644 index 5d00c179ec83..000000000000 --- a/src/Tools/dotnet-watch/test/CommandLineOptionsTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.IO; -using System.Linq; -using System.Text; -using Microsoft.Extensions.CommandLineUtils; -using Microsoft.Extensions.Tools.Internal; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.Tests -{ - public class CommandLineOptionsTests - { - private readonly TestConsole _console; - - public CommandLineOptionsTests(ITestOutputHelper output) - { - _console = new TestConsole(output); - } - - [Theory] - [InlineData(new object[] { new[] { "-h" } })] - [InlineData(new object[] { new[] { "-?" } })] - [InlineData(new object[] { new[] { "--help" } })] - [InlineData(new object[] { new[] { "--help", "--bogus" } })] - [InlineData(new object[] { new[] { "--" } })] - [InlineData(new object[] { new string[0] })] - public void HelpArgs(string[] args) - { - var options = CommandLineOptions.Parse(args, _console); - - Assert.True(options.IsHelp); - Assert.Contains("Usage: dotnet watch ", _console.GetOutput()); - } - - [Theory] - [InlineData(new[] { "run" }, new[] { "run" })] - [InlineData(new[] { "run", "--", "subarg" }, new[] { "run", "--", "subarg" })] - [InlineData(new[] { "--", "run", "--", "subarg" }, new[] { "run", "--", "subarg" })] - [InlineData(new[] { "--unrecognized-arg" }, new[] { "--unrecognized-arg" })] - public void ParsesRemainingArgs(string[] args, string[] expected) - { - var options = CommandLineOptions.Parse(args, _console); - - Assert.Equal(expected, options.RemainingArguments.ToArray()); - Assert.False(options.IsHelp); - Assert.Empty(_console.GetOutput()); - } - - [Fact] - public void CannotHaveQuietAndVerbose() - { - var ex = Assert.Throws(() => CommandLineOptions.Parse(new[] { "--quiet", "--verbose" }, _console)); - Assert.Equal(Resources.Error_QuietAndVerboseSpecified, ex.Message); - } - } -} diff --git a/src/Tools/dotnet-watch/test/ConsoleReporterTests.cs b/src/Tools/dotnet-watch/test/ConsoleReporterTests.cs deleted file mode 100644 index 00adef1ffff0..000000000000 --- a/src/Tools/dotnet-watch/test/ConsoleReporterTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Text; -using Microsoft.Extensions.Tools.Internal; -using Xunit; - -namespace Microsoft.Extensions.Tools.Tests -{ - public class ReporterTests - { - private static readonly string EOL = Environment.NewLine; - - [Fact] - public void WritesToStandardStreams() - { - var testConsole = new TestConsole(); - var reporter = new ConsoleReporter(testConsole, verbose: true, quiet: false); - - // stdout - reporter.Verbose("verbose"); - Assert.Equal("verbose" + EOL, testConsole.GetOutput()); - testConsole.Clear(); - - reporter.Output("out"); - Assert.Equal("out" + EOL, testConsole.GetOutput()); - testConsole.Clear(); - - reporter.Warn("warn"); - Assert.Equal("warn" + EOL, testConsole.GetOutput()); - testConsole.Clear(); - - // stderr - reporter.Error("error"); - Assert.Equal("error" + EOL, testConsole.GetError()); - testConsole.Clear(); - } - - private class TestConsole : IConsole - { - private readonly StringBuilder _out; - private readonly StringBuilder _error; - - public TestConsole() - { - _out = new StringBuilder(); - _error = new StringBuilder(); - Out = new StringWriter(_out); - Error = new StringWriter(_error); - } - - event ConsoleCancelEventHandler IConsole.CancelKeyPress - { - add { } - remove { } - } - - public string GetOutput() => _out.ToString(); - public string GetError() => _error.ToString(); - - public void Clear() - { - _out.Clear(); - _error.Clear(); - } - - public void ResetColor() - { - ForegroundColor = default(ConsoleColor); - } - - public TextWriter Out { get; } - public TextWriter Error { get; } - public TextReader? In { get; } - public bool IsInputRedirected { get; } - public bool IsOutputRedirected { get; } - public bool IsErrorRedirected { get; } - public ConsoleColor ForegroundColor { get; set; } - } - } -} diff --git a/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs b/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs deleted file mode 100644 index 36223caed30c..000000000000 --- a/src/Tools/dotnet-watch/test/DotNetWatcherTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests -{ - public class DotNetWatcherTests : IDisposable - { - private readonly ITestOutputHelper _logger; - private readonly KitchenSinkApp _app; - - public DotNetWatcherTests(ITestOutputHelper logger) - { - _logger = logger; - _app = new KitchenSinkApp(logger); - } - - [Fact] - public async Task RunsWithDotnetWatchEnvVariable() - { - Assert.True(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_WATCH")), "DOTNET_WATCH cannot be set already when this test is running"); - - await _app.StartWatcherAsync(); - const string messagePrefix = "DOTNET_WATCH = "; - var message = await _app.Process.GetOutputLineStartsWithAsync(messagePrefix, TimeSpan.FromMinutes(2)); - var envValue = message.Substring(messagePrefix.Length); - Assert.Equal("1", envValue); - } - - [ConditionalFact] - [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/24841", Queues = "Windows.10.Arm64;Windows.10.Arm64.Open;Windows.10.Arm64v8;Windows.10.Arm64v8.Open")] - public async Task RunsWithIterationEnvVariable() - { - await _app.StartWatcherAsync(); - var source = Path.Combine(_app.SourceDirectory, "Program.cs"); - var contents = File.ReadAllText(source); - const string messagePrefix = "DOTNET_WATCH_ITERATION = "; - for (var i = 1; i <= 3; i++) - { - var message = await _app.Process.GetOutputLineStartsWithAsync(messagePrefix, TimeSpan.FromMinutes(2)); - var count = int.Parse(message.Substring(messagePrefix.Length), CultureInfo.InvariantCulture); - Assert.Equal(i, count); - - await _app.IsWaitingForFileChange(); - - File.SetLastWriteTime(source, DateTime.Now); - await _app.HasRestarted(TimeSpan.FromMinutes(1)); - } - } - - [Fact] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23854")] - public async Task RunsWithNoRestoreOnOrdinaryFileChanges() - { - _app.DotnetWatchArgs.Add("--verbose"); - - await _app.StartWatcherAsync(arguments: new[] { "wait" }); - var source = Path.Combine(_app.SourceDirectory, "Program.cs"); - const string messagePrefix = "watch : Running dotnet with the following arguments: run"; - - // Verify that the first run does not use --no-restore - Assert.Contains(_app.Process.Output, p => string.Equals(messagePrefix + " -- wait", p.Trim())); - - for (var i = 0; i < 3; i++) - { - File.SetLastWriteTime(source, DateTime.Now); - var message = await _app.Process.GetOutputLineStartsWithAsync(messagePrefix, TimeSpan.FromMinutes(2)); - - Assert.Equal(messagePrefix + " --no-restore -- wait", message.Trim()); - - await _app.HasRestarted(); - } - } - - [Fact] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23854")] - public async Task RunsWithRestoreIfCsprojChanges() - { - _app.DotnetWatchArgs.Add("--verbose"); - - await _app.StartWatcherAsync(arguments: new[] { "wait" }); - var source = Path.Combine(_app.SourceDirectory, "KitchenSink.csproj"); - const string messagePrefix = "watch : Running dotnet with the following arguments: run"; - - // Verify that the first run does not use --no-restore - Assert.Contains(_app.Process.Output, p => string.Equals(messagePrefix + " -- wait", p.Trim())); - - File.SetLastWriteTime(source, DateTime.Now); - var message = await _app.Process.GetOutputLineStartsWithAsync(messagePrefix, TimeSpan.FromMinutes(2)); - - // csproj changed. Do not expect a --no-restore - Assert.Equal(messagePrefix + " -- wait", message.Trim()); - - await _app.HasRestarted(); - - // regular file changed after csproj changes. Should use --no-restore - File.SetLastWriteTime(Path.Combine(_app.SourceDirectory, "Program.cs"), DateTime.Now); - message = await _app.Process.GetOutputLineStartsWithAsync(messagePrefix, TimeSpan.FromMinutes(2)); - Assert.Equal(messagePrefix + " --no-restore -- wait", message.Trim()); - } - - public void Dispose() - { - _app.Dispose(); - } - - private class KitchenSinkApp : WatchableApp - { - public KitchenSinkApp(ITestOutputHelper logger) - : base("KitchenSink", logger) - { - } - } - } -} diff --git a/src/Tools/dotnet-watch/test/FileWatcherTests.cs b/src/Tools/dotnet-watch/test/FileWatcherTests.cs deleted file mode 100644 index edc542e7bcc9..000000000000 --- a/src/Tools/dotnet-watch/test/FileWatcherTests.cs +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; -using Microsoft.DotNet.Watcher.Internal; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests -{ - public class FileWatcherTests - { - public FileWatcherTests(ITestOutputHelper output) - { - _output = output; - } - - private readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60); - private readonly TimeSpan NegativeTimeout = TimeSpan.FromSeconds(5); - private readonly ITestOutputHelper _output; - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task NewFile(bool usePolling) - { - await UsingTempDirectory(async dir => - { - using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) - { - var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var filesChanged = new HashSet(); - - watcher.OnFileChange += (_, f) => - { - filesChanged.Add(f); - changedEv.TrySetResult(0); - }; - watcher.EnableRaisingEvents = true; - - var testFileFullPath = Path.Combine(dir, "foo"); - File.WriteAllText(testFileFullPath, string.Empty); - - await changedEv.Task.TimeoutAfter(DefaultTimeout); - Assert.Equal(testFileFullPath, filesChanged.Single()); - } - }); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task ChangeFile(bool usePolling) - { - await UsingTempDirectory(async dir => - { - var testFileFullPath = Path.Combine(dir, "foo"); - File.WriteAllText(testFileFullPath, string.Empty); - - using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) - { - var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var filesChanged = new HashSet(); - - EventHandler? handler = null; - handler = (_, f) => - { - watcher.EnableRaisingEvents = false; - watcher.OnFileChange -= handler; - - filesChanged.Add(f); - changedEv.TrySetResult(0); - }; - - watcher.OnFileChange += handler; - watcher.EnableRaisingEvents = true; - - // On Unix the file write time is in 1s increments; - // if we don't wait, there's a chance that the polling - // watcher will not detect the change - await Task.Delay(1000); - File.WriteAllText(testFileFullPath, string.Empty); - - await changedEv.Task.TimeoutAfter(DefaultTimeout); - Assert.Equal(testFileFullPath, filesChanged.Single()); - } - }); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task MoveFile(bool usePolling) - { - await UsingTempDirectory(async dir => - { - var srcFile = Path.Combine(dir, "foo"); - var dstFile = Path.Combine(dir, "foo2"); - - File.WriteAllText(srcFile, string.Empty); - - using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) - { - var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var filesChanged = new HashSet(); - - EventHandler? handler = null; - handler = (_, f) => - { - filesChanged.Add(f); - - if (filesChanged.Count >= 2) - { - watcher.EnableRaisingEvents = false; - watcher.OnFileChange -= handler; - - changedEv.TrySetResult(0); - } - }; - - watcher.OnFileChange += handler; - watcher.EnableRaisingEvents = true; - - File.Move(srcFile, dstFile); - - await changedEv.Task.TimeoutAfter(DefaultTimeout); - Assert.Contains(srcFile, filesChanged); - Assert.Contains(dstFile, filesChanged); - } - }); - } - - [Fact] - public async Task FileInSubdirectory() - { - await UsingTempDirectory(async dir => - { - var subdir = Path.Combine(dir, "subdir"); - Directory.CreateDirectory(subdir); - - var testFileFullPath = Path.Combine(subdir, "foo"); - File.WriteAllText(testFileFullPath, string.Empty); - - using (var watcher = FileWatcherFactory.CreateWatcher(dir, true)) - { - var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var filesChanged = new HashSet(); - - EventHandler? handler = null; - handler = (_, f) => - { - filesChanged.Add(f); - - if (filesChanged.Count >= 2) - { - watcher.EnableRaisingEvents = false; - watcher.OnFileChange -= handler; - changedEv.TrySetResult(0); - } - }; - - watcher.OnFileChange += handler; - watcher.EnableRaisingEvents = true; - - // On Unix the file write time is in 1s increments; - // if we don't wait, there's a chance that the polling - // watcher will not detect the change - await Task.Delay(1000); - File.WriteAllText(testFileFullPath, string.Empty); - - await changedEv.Task.TimeoutAfter(DefaultTimeout); - Assert.Contains(subdir, filesChanged); - Assert.Contains(testFileFullPath, filesChanged); - } - }); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task NoNotificationIfDisabled(bool usePolling) - { - await UsingTempDirectory(async dir => - { - using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) - { - var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - watcher.OnFileChange += (_, f) => changedEv.TrySetResult(0); - - // Disable - watcher.EnableRaisingEvents = false; - - var testFileFullPath = Path.Combine(dir, "foo"); - - // On Unix the file write time is in 1s increments; - // if we don't wait, there's a chance that the polling - // watcher will not detect the change - await Task.Delay(1000); - File.WriteAllText(testFileFullPath, string.Empty); - - await Assert.ThrowsAsync(() => changedEv.Task.TimeoutAfter(NegativeTimeout)); - } - }); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task DisposedNoEvents(bool usePolling) - { - await UsingTempDirectory(async dir => - { - var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) - { - watcher.OnFileChange += (_, f) => changedEv.TrySetResult(0); - watcher.EnableRaisingEvents = true; - } - - var testFileFullPath = Path.Combine(dir, "foo"); - - // On Unix the file write time is in 1s increments; - // if we don't wait, there's a chance that the polling - // watcher will not detect the change - await Task.Delay(1000); - File.WriteAllText(testFileFullPath, string.Empty); - - await Assert.ThrowsAsync(() => changedEv.Task.TimeoutAfter(NegativeTimeout)); - }); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task MultipleFiles(bool usePolling) - { - await UsingTempDirectory(async dir => - { - File.WriteAllText(Path.Combine(dir, "foo1"), string.Empty); - File.WriteAllText(Path.Combine(dir, "foo2"), string.Empty); - File.WriteAllText(Path.Combine(dir, "foo3"), string.Empty); - File.WriteAllText(Path.Combine(dir, "foo4"), string.Empty); - - // On Unix the native file watcher may surface events from - // the recent past. Delay to avoid those. - // On Unix the file write time is in 1s increments; - // if we don't wait, there's a chance that the polling - // watcher will not detect the change - await Task.Delay(1250); - - var testFileFullPath = Path.Combine(dir, "foo3"); - - using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) - { - var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var filesChanged = new HashSet(); - - EventHandler? handler = null; - handler = (_, f) => - { - watcher.EnableRaisingEvents = false; - watcher.OnFileChange -= handler; - filesChanged.Add(f); - changedEv.TrySetResult(0); - }; - - watcher.OnFileChange += handler; - watcher.EnableRaisingEvents = true; - - File.WriteAllText(testFileFullPath, string.Empty); - - await changedEv.Task.TimeoutAfter(DefaultTimeout); - Assert.Equal(testFileFullPath, filesChanged.Single()); - } - }); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task MultipleTriggers(bool usePolling) - { - var filesChanged = new HashSet(); - - await UsingTempDirectory(async dir => - { - using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) - { - watcher.EnableRaisingEvents = true; - - for (var i = 0; i < 5; i++) - { - await AssertFileChangeRaisesEvent(dir, watcher); - } - - watcher.EnableRaisingEvents = false; - } - }); - } - - private async Task AssertFileChangeRaisesEvent(string directory, IFileSystemWatcher watcher) - { - var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var expectedPath = Path.Combine(directory, Path.GetRandomFileName()); - EventHandler handler = (object? _, string f) => - { - _output.WriteLine("File changed: " + f); - try - { - if (string.Equals(f, expectedPath, StringComparison.OrdinalIgnoreCase)) - { - changedEv.TrySetResult(0); - } - } - catch (ObjectDisposedException) - { - // There's a known race condition here: - // even though we tell the watcher to stop raising events and we unsubscribe the handler - // there might be in-flight events that will still process. Since we dispose the reset - // event, this code will fail if the handler executes after Dispose happens. - } - }; - - File.AppendAllText(expectedPath, " "); - - watcher.OnFileChange += handler; - try - { - // On Unix the file write time is in 1s increments; - // if we don't wait, there's a chance that the polling - // watcher will not detect the change - await Task.Delay(1000); - File.AppendAllText(expectedPath, " "); - await changedEv.Task.TimeoutAfter(DefaultTimeout); - } - finally - { - watcher.OnFileChange -= handler; - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task DeleteSubfolder(bool usePolling) - { - await UsingTempDirectory(async dir => - { - var subdir = Path.Combine(dir, "subdir"); - Directory.CreateDirectory(subdir); - - var f1 = Path.Combine(subdir, "foo1"); - var f2 = Path.Combine(subdir, "foo2"); - var f3 = Path.Combine(subdir, "foo3"); - - File.WriteAllText(f1, string.Empty); - File.WriteAllText(f2, string.Empty); - File.WriteAllText(f3, string.Empty); - - using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling)) - { - var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var filesChanged = new HashSet(); - - EventHandler? handler = null; - handler = (_, f) => - { - filesChanged.Add(f); - - if (filesChanged.Count >= 4) - { - watcher.EnableRaisingEvents = false; - watcher.OnFileChange -= handler; - changedEv.TrySetResult(0); - } - }; - - watcher.OnFileChange += handler; - watcher.EnableRaisingEvents = true; - - Directory.Delete(subdir, recursive: true); - - await changedEv.Task.TimeoutAfter(DefaultTimeout); - - Assert.Contains(f1, filesChanged); - Assert.Contains(f2, filesChanged); - Assert.Contains(f3, filesChanged); - Assert.Contains(subdir, filesChanged); - } - }); - } - - private static async Task UsingTempDirectory(Func func) - { - var tempFolder = Path.Combine(Path.GetTempPath(), $"{nameof(FileWatcherTests)}-{Guid.NewGuid().ToString("N")}"); - if (Directory.Exists(tempFolder)) - { - Directory.Delete(tempFolder, recursive: true); - } - - Directory.CreateDirectory(tempFolder); - - try - { - await func(tempFolder); - } - finally - { - Directory.Delete(tempFolder, recursive: true); - } - } - } -} diff --git a/src/Tools/dotnet-watch/test/GlobbingAppTests.cs b/src/Tools/dotnet-watch/test/GlobbingAppTests.cs deleted file mode 100644 index 4d17dc1f0210..000000000000 --- a/src/Tools/dotnet-watch/test/GlobbingAppTests.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; -using Microsoft.DotNet.Watcher.Tools.Tests; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests -{ - public class GlobbingAppTests : IDisposable - { - private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60); - - private readonly GlobbingApp _app; - - public GlobbingAppTests(ITestOutputHelper logger) - { - _app = new GlobbingApp(logger); - } - - [ConditionalTheory] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/27856")] - [InlineData(true)] - [InlineData(false)] - [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Fedora.33.Amd64.Open)Ubuntu.1604.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-33-helix-20210120000908-a9df267")] - public async Task ChangeCompiledFile(bool usePollingWatcher) - { - _app.UsePollingWatcher = usePollingWatcher; - await _app.StartWatcherAsync().TimeoutAfter(DefaultTimeout); - - var types = await _app.GetCompiledAppDefinedTypes().TimeoutAfter(DefaultTimeout); - Assert.Equal(2, types); - - var fileToChange = Path.Combine(_app.SourceDirectory, "include", "Foo.cs"); - var programCs = File.ReadAllText(fileToChange); - File.WriteAllText(fileToChange, programCs); - - await _app.HasRestarted().TimeoutAfter(DefaultTimeout); - types = await _app.GetCompiledAppDefinedTypes().TimeoutAfter(DefaultTimeout); - Assert.Equal(2, types); - } - - [ConditionalFact] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/25967")] - [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036")] - public async Task DeleteCompiledFile() - { - await _app.StartWatcherAsync().TimeoutAfter(DefaultTimeout); - - var types = await _app.GetCompiledAppDefinedTypes().TimeoutAfter(DefaultTimeout); - Assert.Equal(2, types); - - var fileToChange = Path.Combine(_app.SourceDirectory, "include", "Foo.cs"); - File.Delete(fileToChange); - - await _app.HasRestarted().TimeoutAfter(DefaultTimeout); - types = await _app.GetCompiledAppDefinedTypes().TimeoutAfter(DefaultTimeout); - Assert.Equal(1, types); - } - - [ConditionalFact] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/25967")] - [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036")] - public async Task DeleteSourceFolder() - { - await _app.StartWatcherAsync().TimeoutAfter(DefaultTimeout); - - var types = await _app.GetCompiledAppDefinedTypes().TimeoutAfter(DefaultTimeout); - Assert.Equal(2, types); - - var folderToDelete = Path.Combine(_app.SourceDirectory, "include"); - Directory.Delete(folderToDelete, recursive: true); - - await _app.HasRestarted().TimeoutAfter(DefaultTimeout); - types = await _app.GetCompiledAppDefinedTypes().TimeoutAfter(DefaultTimeout); - Assert.Equal(1, types); - } - - [ConditionalFact] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/25967")] - [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036")] - public async Task RenameCompiledFile() - { - await _app.StartWatcherAsync().TimeoutAfter(DefaultTimeout); - - var oldFile = Path.Combine(_app.SourceDirectory, "include", "Foo.cs"); - var newFile = Path.Combine(_app.SourceDirectory, "include", "Foo_new.cs"); - File.Move(oldFile, newFile); - - await _app.HasRestarted().TimeoutAfter(DefaultTimeout); - } - - [Fact] - public async Task ChangeExcludedFile() - { - await _app.StartWatcherAsync().TimeoutAfter(DefaultTimeout); - - var changedFile = Path.Combine(_app.SourceDirectory, "exclude", "Baz.cs"); - File.WriteAllText(changedFile, ""); - - var restart = _app.HasRestarted(); - var finished = await Task.WhenAny(Task.Delay(TimeSpan.FromSeconds(10)), restart); - Assert.NotSame(restart, finished); - } - - [Fact] - public async Task ListsFiles() - { - await _app.PrepareAsync().TimeoutAfter(DefaultTimeout); - _app.Start(new[] { "--list" }); - var cts = new CancellationTokenSource(); - cts.CancelAfter(TimeSpan.FromSeconds(30)); - var lines = await _app.Process.GetAllOutputLinesAsync(cts.Token).TimeoutAfter(DefaultTimeout); - var files = lines.Where(l => !l.StartsWith("watch :", StringComparison.Ordinal)); - - AssertEx.EqualFileList( - _app.Scenario.WorkFolder, - new[] - { - "GlobbingApp/Program.cs", - "GlobbingApp/include/Foo.cs", - "GlobbingApp/GlobbingApp.csproj", - }, - files); - } - - public void Dispose() - { - _app.Dispose(); - } - - private class GlobbingApp : WatchableApp - { - public GlobbingApp(ITestOutputHelper logger) - : base("GlobbingApp", logger) - { - } - - public async Task GetCompiledAppDefinedTypes() - { - var definedTypesMessage = await Process!.GetOutputLineStartsWithAsync("Defined types = ", TimeSpan.FromSeconds(30)); - return int.Parse(definedTypesMessage.Split('=').Last(), CultureInfo.InvariantCulture); - } - } - } -} diff --git a/src/Tools/dotnet-watch/test/MSBuildEvaluationFilterTest.cs b/src/Tools/dotnet-watch/test/MSBuildEvaluationFilterTest.cs deleted file mode 100644 index 0781ab02b9b8..000000000000 --- a/src/Tools/dotnet-watch/test/MSBuildEvaluationFilterTest.cs +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Moq; -using Xunit; - -namespace Microsoft.DotNet.Watcher.Tools -{ - public class MSBuildEvaluationFilterTest - { - private readonly IFileSetFactory _fileSetFactory = Mock.Of( - f => f.CreateAsync(It.IsAny()) == Task.FromResult(FileSet.Empty)); - - [Fact] - public async Task ProcessAsync_EvaluatesFileSetIfProjFileChanges() - { - // Arrange - var filter = new MSBuildEvaluationFilter(_fileSetFactory); - var context = new DotNetWatchContext - { - Iteration = 0, - }; - - await filter.ProcessAsync(context, default); - - context.Iteration++; - context.ChangedFile = new FileItem("Test.csproj"); - context.RequiresMSBuildRevaluation = false; - - // Act - await filter.ProcessAsync(context, default); - - // Assert - Assert.True(context.RequiresMSBuildRevaluation); - } - - [Fact] - public async Task ProcessAsync_DoesNotEvaluateFileSetIfNonProjFileChanges() - { - // Arrange - var filter = new MSBuildEvaluationFilter(_fileSetFactory); - var context = new DotNetWatchContext - { - Iteration = 0, - }; - - await filter.ProcessAsync(context, default); - - context.Iteration++; - context.ChangedFile = new FileItem("Controller.cs"); - context.RequiresMSBuildRevaluation = false; - - // Act - await filter.ProcessAsync(context, default); - - // Assert - Assert.False(context.RequiresMSBuildRevaluation); - Mock.Get(_fileSetFactory).Verify(v => v.CreateAsync(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task ProcessAsync_EvaluateFileSetOnEveryChangeIfOptimizationIsSuppressed() - { - // Arrange - var filter = new MSBuildEvaluationFilter(_fileSetFactory); - var context = new DotNetWatchContext - { - Iteration = 0, - SuppressMSBuildIncrementalism = true, - }; - - await filter.ProcessAsync(context, default); - - context.Iteration++; - context.ChangedFile = new FileItem("Controller.cs"); - context.RequiresMSBuildRevaluation = false; - - // Act - await filter.ProcessAsync(context, default); - - // Assert - Assert.True(context.RequiresMSBuildRevaluation); - Mock.Get(_fileSetFactory).Verify(v => v.CreateAsync(It.IsAny()), Times.Exactly(2)); - - } - - [Fact] - public async Task ProcessAsync_SetsEvaluationRequired_IfMSBuildFileChanges_ButIsNotChangedFile() - { - // There's a chance that the watcher does not correctly report edits to msbuild files on - // concurrent edits. MSBuildEvaluationFilter uses timestamps to additionally track changes to these files. - - // Arrange - var fileSet = new FileSet(false, new[] { new FileItem("Controlller.cs"), new FileItem("Proj.csproj") }); - var fileSetFactory = Mock.Of(f => f.CreateAsync(It.IsAny()) == Task.FromResult(fileSet)); - - var filter = new TestableMSBuildEvaluationFilter(fileSetFactory) - { - Timestamps = - { - ["Controller.cs"] = new DateTime(1000), - ["Proj.csproj"] = new DateTime(1000), - } - }; - var context = new DotNetWatchContext - { - Iteration = 0, - }; - - await filter.ProcessAsync(context, default); - context.RequiresMSBuildRevaluation = false; - context.ChangedFile = new FileItem("Controller.cs"); - context.Iteration++; - filter.Timestamps["Proj.csproj"] = new DateTime(1007); - - // Act - await filter.ProcessAsync(context, default); - - // Assert - Assert.True(context.RequiresMSBuildRevaluation); - } - - public class TestableMSBuildEvaluationFilter : MSBuildEvaluationFilter - { - public TestableMSBuildEvaluationFilter(IFileSetFactory factory) - : base(factory) - { - } - - public Dictionary Timestamps { get; } = new Dictionary(); - - protected override DateTime GetLastWriteTimeUtcSafely(string file) => Timestamps[file]; - } - } -} diff --git a/src/Tools/dotnet-watch/test/MsBuildFileSetFactoryTest.cs b/src/Tools/dotnet-watch/test/MsBuildFileSetFactoryTest.cs deleted file mode 100644 index 052c1e0cc890..000000000000 --- a/src/Tools/dotnet-watch/test/MsBuildFileSetFactoryTest.cs +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; -using Microsoft.DotNet.Watcher.Internal; -using Microsoft.Extensions.Tools.Internal; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.Tests -{ - using ItemSpec = TemporaryCSharpProject.ItemSpec; - - public class MsBuildFileSetFactoryTest : IDisposable - { - private readonly IReporter _reporter; - private readonly TemporaryDirectory _tempDir; - public MsBuildFileSetFactoryTest(ITestOutputHelper output) - { - _reporter = new TestReporter(output); - _tempDir = new TemporaryDirectory(); - } - - [Fact] - public async Task FindsCustomWatchItems() - { - _tempDir - .WithCSharpProject("Project1", out var target) - .WithTargetFrameworks("netcoreapp1.0") - .WithItem(new ItemSpec { Name = "Watch", Include = "*.js", Exclude = "gulpfile.js" }) - .Dir() - .WithFile("Program.cs") - .WithFile("app.js") - .WithFile("gulpfile.js"); - - var fileset = await GetFileSet(target); - - AssertEx.EqualFileList( - _tempDir.Root, - new[] - { - "Project1.csproj", - "Program.cs", - "app.js" - }, - fileset - ); - } - - [Fact] - public async Task ExcludesDefaultItemsWithWatchFalseMetadata() - { - _tempDir - .WithCSharpProject("Project1", out var target) - .WithTargetFrameworks("net40") - .WithItem(new ItemSpec { Name = "EmbeddedResource", Update = "*.resx", Watch = false }) - .Dir() - .WithFile("Program.cs") - .WithFile("Strings.resx"); - - var fileset = await GetFileSet(target); - - AssertEx.EqualFileList( - _tempDir.Root, - new[] - { - "Project1.csproj", - "Program.cs", - }, - fileset - ); - } - - [Fact] - public async Task SingleTfm() - { - _tempDir - .SubDir("src") - .SubDir("Project1") - .WithCSharpProject("Project1", out var target) - .WithProperty("BaseIntermediateOutputPath", "obj") - .WithTargetFrameworks("netcoreapp1.0") - .Dir() - .WithFile("Program.cs") - .WithFile("Class1.cs") - .SubDir("obj").WithFile("ignored.cs").Up() - .SubDir("Properties").WithFile("Strings.resx").Up() - .Up() - .Up() - .Create(); - - var fileset = await GetFileSet(target); - - AssertEx.EqualFileList( - _tempDir.Root, - new[] - { - "src/Project1/Project1.csproj", - "src/Project1/Program.cs", - "src/Project1/Class1.cs", - "src/Project1/Properties/Strings.resx", - }, - fileset - ); - } - - [Fact] - public async Task IncludesContentFiles() - { - _tempDir - .SubDir("src") - .SubDir("Project1") - .WithCSharpProject("Project1", out var target, sdk: "Microsoft.NET.Sdk.Web") - .WithProperty("BaseIntermediateOutputPath", "obj") - .WithTargetFrameworks("net5.0") - .Dir() - .WithFile("Program.cs") - .SubDir("wwwroot") - .SubDir("css").WithFile("app.css").Up() - .SubDir("js").WithFile("site.js").Up() - .WithFile("favicon.ico") - .Up() - .SubDir("obj").WithFile("ignored.cs").Up() - .Up() - .Up() - .Create(); - var expected = new FileSet( - isNetCoreApp31OrNewer: true, - new[] - { - new FileItem($"{_tempDir.Root}/src/Project1/Project1.csproj"), - new FileItem($"{_tempDir.Root}/src/Project1/Program.cs"), - new FileItem($"{_tempDir.Root}/src/Project1/wwwroot/css/app.css", FileKind.StaticFile, "css/app.css"), - new FileItem($"{_tempDir.Root}/src/Project1/wwwroot/js/site.js", FileKind.StaticFile, "js/site.js"), - new FileItem($"{_tempDir.Root}/src/Project1/wwwroot/favicon.ico", FileKind.StaticFile, "favicon.ico"), - }); - - var fileset = await GetFileSet(target); - - AssertEx.EqualFileList(expected, fileset); - } - - [Fact] - public async Task IncludesContentFilesFromRCLS() - { - _tempDir - .SubDir("src") - .SubDir("RCL1") - .WithCSharpProject("RCL1", out var rcl, sdk: "Microsoft.NET.Sdk.Razor") - .WithTargetFrameworks("net5.0") - .Dir() - .WithFile("Index.razor") - .SubDir("wwwroot") - .SubDir("css").WithFile("app.css").Up() - .SubDir("js").WithFile("site.js").Up() - .WithFile("favicon.ico") - .Up() - .Up() - .SubDir("Project1") - .WithCSharpProject("Project1", out var target, sdk: "Microsoft.NET.Sdk.Web") - .WithProjectReference(rcl) - .WithTargetFrameworks("net5.0") - .Dir() - .WithFile("Program.cs") - .SubDir("obj").WithFile("ignored.cs").Up() - .Up() - .Up() - .Create(); - var expected = new FileSet( - isNetCoreApp31OrNewer: true, - new[] - { - new FileItem($"{_tempDir.Root}/src/Project1/Project1.csproj"), - new FileItem($"{_tempDir.Root}/src/Project1/Program.cs"), - new FileItem($"{_tempDir.Root}/src/RCL1/RCL1.csproj"), - new FileItem($"{_tempDir.Root}/src/RCL1/Index.razor"), - new FileItem($"{_tempDir.Root}/src/RCL1/wwwroot/css/app.css", FileKind.StaticFile, "_content/RCL1/css/app.css"), - new FileItem($"{_tempDir.Root}/src/RCL1/wwwroot/js/site.js", FileKind.StaticFile, "_content/RCL1/js/site.js"), - new FileItem($"{_tempDir.Root}/src/RCL1/wwwroot/favicon.ico", FileKind.StaticFile, "_content/RCL1/favicon.ico"), - }); - - var fileset = await GetFileSet(target); - - AssertEx.EqualFileList(expected, fileset); - } - - [Fact] - public async Task IgnoreContentFilesWhenSuppressHandlingStaticContentFilesIsConfigured() - { - _tempDir - .SubDir("src") - .SubDir("Project1") - .WithCSharpProject("Project1", out var target, sdk: "Microsoft.NET.Sdk.Web") - .WithProperty("BaseIntermediateOutputPath", "obj") - .WithTargetFrameworks("net5.0") - .Dir() - .WithFile("Program.cs") - .SubDir("wwwroot") - .SubDir("css").WithFile("app.css").Up() - .SubDir("js").WithFile("site.js").Up() - .WithFile("favicon.ico") - .Up() - .SubDir("obj").WithFile("ignored.cs").Up() - .Up() - .Up() - .Create(); - var expected = new FileSet( - isNetCoreApp31OrNewer: true, - new[] - { - new FileItem($"{_tempDir.Root}/src/Project1/Project1.csproj"), - new FileItem($"{_tempDir.Root}/src/Project1/Program.cs"), - }); - - var watchOptions = DotNetWatchOptions.Default with { SuppressHandlingStaticContentFiles = true }; - var fileset = await GetFileSet(new MsBuildFileSetFactory(_reporter, watchOptions, target.Path, waitOnError: false, trace: true)); - - AssertEx.EqualFileList(expected, fileset); - } - - [Fact] - public async Task MultiTfm() - { - _tempDir - .SubDir("src") - .SubDir("Project1") - .WithCSharpProject("Project1", out var target) - .WithTargetFrameworks("netcoreapp1.0", "net451") - .WithProperty("EnableDefaultCompileItems", "false") - .WithItem("Compile", "Class1.netcore.cs", "'$(TargetFramework)'=='netcoreapp1.0'") - .WithItem("Compile", "Class1.desktop.cs", "'$(TargetFramework)'=='net451'") - .Dir() - .WithFile("Class1.netcore.cs") - .WithFile("Class1.desktop.cs") - .WithFile("Class1.notincluded.cs") - .Up() - .Up() - .Create(); - - var fileset = await GetFileSet(target); - - AssertEx.EqualFileList( - _tempDir.Root, - new[] - { - "src/Project1/Project1.csproj", - "src/Project1/Class1.netcore.cs", - "src/Project1/Class1.desktop.cs", - }, - fileset - ); - } - - [Fact] - public async Task ProjectReferences_OneLevel() - { - _tempDir - .SubDir("src") - .SubDir("Project2") - .WithCSharpProject("Project2", out var proj2) - .WithTargetFrameworks("netstandard1.1") - .Dir() - .WithFile("Class2.cs") - .Up() - .SubDir("Project1") - .WithCSharpProject("Project1", out var target) - .WithTargetFrameworks("netcoreapp1.0", "net451") - .WithProjectReference(proj2) - .Dir() - .WithFile("Class1.cs") - .Up() - .Up() - .Create(); - - var fileset = await GetFileSet(target); - - AssertEx.EqualFileList( - _tempDir.Root, - new[] - { - "src/Project2/Project2.csproj", - "src/Project2/Class2.cs", - "src/Project1/Project1.csproj", - "src/Project1/Class1.cs", - }, - fileset - ); - } - - [Fact] - public async Task TransitiveProjectReferences_TwoLevels() - { - _tempDir - .SubDir("src") - .SubDir("Project3") - .WithCSharpProject("Project3", out var proj3) - .WithTargetFrameworks("netstandard1.0") - .Dir() - .WithFile("Class3.cs") - .Up() - .SubDir("Project2") - .WithCSharpProject("Project2", out TemporaryCSharpProject proj2) - .WithTargetFrameworks("netstandard1.1") - .WithProjectReference(proj3) - .Dir() - .WithFile("Class2.cs") - .Up() - .SubDir("Project1") - .WithCSharpProject("Project1", out TemporaryCSharpProject target) - .WithTargetFrameworks("netcoreapp1.0", "net451") - .WithProjectReference(proj2) - .Dir() - .WithFile("Class1.cs"); - - var fileset = await GetFileSet(target); - - AssertEx.EqualFileList( - _tempDir.Root, - new[] - { - "src/Project3/Project3.csproj", - "src/Project3/Class3.cs", - "src/Project2/Project2.csproj", - "src/Project2/Class2.cs", - "src/Project1/Project1.csproj", - "src/Project1/Class1.cs", - }, - fileset - ); - } - - [Fact] - public async Task ProjectReferences_Graph() - { - var graph = new TestProjectGraph(_tempDir); - graph.OnCreate(p => p.WithTargetFrameworks("net45")); - var matches = Regex.Matches(@" - A->B B->C C->D D->E - B->E - A->F F->G G->E - F->E - W->U - Y->Z - Y->B - Y->F", - @"(\w)->(\w)"); - - Assert.Equal(13, matches.Count); - foreach (Match m in matches) - { - var target = graph.GetOrCreate(m.Groups[2].Value); - graph.GetOrCreate(m.Groups[1].Value).WithProjectReference(target); - } - - var a = graph.Find("A"); - Assert.NotNull(a); - - a!.WithProjectReference(graph.Find("W"), watch: false); - - var output = new OutputSink(); - var filesetFactory = new MsBuildFileSetFactory(_reporter, DotNetWatchOptions.Default, graph.GetOrCreate("A").Path, output, trace: true); - - var fileset = await GetFileSet(filesetFactory); - - _reporter.Output(string.Join( - Environment.NewLine, - output.Current.Lines.Select(l => "Sink output: " + l))); - - var includedProjects = new[] { "A", "B", "C", "D", "E", "F", "G" }; - AssertEx.EqualFileList( - _tempDir.Root, - includedProjects - .Select(p => $"{p}/{p}.csproj"), - fileset - ); - - // ensure unreachable projects exist but where not included - Assert.NotNull(graph.Find("W")); - Assert.NotNull(graph.Find("U")); - Assert.NotNull(graph.Find("Y")); - Assert.NotNull(graph.Find("Z")); - - // ensure each project is only visited once for collecting watch items - Assert.All(includedProjects, - projectName => - Assert.Single(output.Current.Lines, - line => line.Contains($"Collecting watch items from '{projectName}'")) - ); - } - - private Task GetFileSet(TemporaryCSharpProject target) - => GetFileSet(new MsBuildFileSetFactory(_reporter, DotNetWatchOptions.Default, target.Path, waitOnError: false, trace: true)); - - private async Task GetFileSet(MsBuildFileSetFactory filesetFactory) - { - _tempDir.Create(); - return await filesetFactory - .CreateAsync(CancellationToken.None) - .TimeoutAfter(TimeSpan.FromSeconds(30)); - } - - public void Dispose() - { - _tempDir.Dispose(); - } - } -} diff --git a/src/Tools/dotnet-watch/test/NoDepsAppTests.cs b/src/Tools/dotnet-watch/test/NoDepsAppTests.cs deleted file mode 100644 index 1eeb5c543cb9..000000000000 --- a/src/Tools/dotnet-watch/test/NoDepsAppTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests -{ - public class NoDepsAppTests : IDisposable - { - private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30); - - private readonly WatchableApp _app; - private readonly ITestOutputHelper _output; - - public NoDepsAppTests(ITestOutputHelper logger) - { - _app = new WatchableApp("NoDepsApp", logger); - _output = logger; - } - - [ConditionalFact] - [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/23360", Queues = "Debian.9.Arm64;Debian.9.Arm64.Open;(Debian.9.Arm64.Open)Ubuntu.1804.Armarch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036;(Debian.9.Arm64)Ubuntu.1804.Armarch@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm64v8-a12566d-20190807161036")] - public async Task RestartProcessOnFileChange() - { - await _app.StartWatcherAsync(new[] { "--no-exit" }); - var processIdentifier = await _app.GetProcessIdentifier(); - - // Then wait for it to restart when we change a file - var fileToChange = Path.Combine(_app.SourceDirectory, "Program.cs"); - var programCs = File.ReadAllText(fileToChange); - File.WriteAllText(fileToChange, programCs); - - await _app.HasRestarted(); - Assert.DoesNotContain(_app.Process.Output, l => l.StartsWith("Exited with error code", StringComparison.Ordinal)); - - var processIdentifier2 = await _app.GetProcessIdentifier(); - Assert.NotEqual(processIdentifier, processIdentifier2); - } - - [ConditionalFact] - [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/24841", Queues = "Windows.10.Arm64;Windows.10.Arm64.Open;Windows.10.Arm64v8;Windows.10.Arm64v8.Open")] - public async Task RestartProcessThatTerminatesAfterFileChange() - { - await _app.StartWatcherAsync(); - var processIdentifier = await _app.GetProcessIdentifier(); - await _app.HasExited(); // process should exit after run - await _app.IsWaitingForFileChange(); - - var fileToChange = Path.Combine(_app.SourceDirectory, "Program.cs"); - - try - { - File.SetLastWriteTime(fileToChange, DateTime.Now); - await _app.HasRestarted(); - } - catch - { - // retry - File.SetLastWriteTime(fileToChange, DateTime.Now); - await _app.HasRestarted(); - } - - var processIdentifier2 = await _app.GetProcessIdentifier(); - Assert.NotEqual(processIdentifier, processIdentifier2); - await _app.HasExited(); // process should exit after run - } - - public void Dispose() - { - _app.Dispose(); - } - } -} diff --git a/src/Tools/dotnet-watch/test/NoRestoreFilterTest.cs b/src/Tools/dotnet-watch/test/NoRestoreFilterTest.cs deleted file mode 100644 index 3e159a22c606..000000000000 --- a/src/Tools/dotnet-watch/test/NoRestoreFilterTest.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.DotNet.Watcher.Tools -{ - public class NoRestoreFilterTest - { - private readonly string[] _arguments = new[] { "run" }; - - [Fact] - public async Task ProcessAsync_LeavesArgumentsUnchangedOnFirstRun() - { - // Arrange - var filter = new NoRestoreFilter(); - - var context = new DotNetWatchContext - { - ProcessSpec = new ProcessSpec - { - Arguments = _arguments, - } - }; - - // Act - await filter.ProcessAsync(context, default); - - // Assert - Assert.Same(_arguments, context.ProcessSpec.Arguments); - } - - [Fact] - public async Task ProcessAsync_LeavesArgumentsUnchangedIfMsBuildRevaluationIsRequired() - { - // Arrange - var filter = new NoRestoreFilter(); - - var context = new DotNetWatchContext - { - Iteration = 0, - ProcessSpec = new ProcessSpec - { - Arguments = _arguments, - } - }; - await filter.ProcessAsync(context, default); - - context.ChangedFile = new FileItem("Test.proj"); - context.RequiresMSBuildRevaluation = true; - context.Iteration++; - - // Act - await filter.ProcessAsync(context, default); - - // Assert - Assert.Same(_arguments, context.ProcessSpec.Arguments); - } - - [Fact] - public async Task ProcessAsync_LeavesArgumentsUnchangedIfOptimizationIsSuppressed() - { - // Arrange - var filter = new NoRestoreFilter(); - - var context = new DotNetWatchContext - { - Iteration = 0, - ProcessSpec = new ProcessSpec - { - Arguments = _arguments, - }, - SuppressMSBuildIncrementalism = true, - }; - await filter.ProcessAsync(context, default); - - context.ChangedFile = new FileItem("Program.cs"); - context.Iteration++; - - // Act - await filter.ProcessAsync(context, default); - - // Assert - Assert.Same(_arguments, context.ProcessSpec.Arguments); - } - - [Fact] - public async Task ProcessAsync_AddsNoRestoreSwitch() - { - // Arrange - var filter = new NoRestoreFilter(); - - var context = new DotNetWatchContext - { - Iteration = 0, - ProcessSpec = new ProcessSpec - { - Arguments = _arguments, - } - }; - await filter.ProcessAsync(context, default); - - context.ChangedFile = new FileItem("Program.cs"); - context.Iteration++; - - // Act - await filter.ProcessAsync(context, default); - - // Assert - Assert.Equal(new[] { "run", "--no-restore" }, context.ProcessSpec.Arguments); - } - - [Fact] - public async Task ProcessAsync_AddsNoRestoreSwitch_WithAdditionalArguments() - { - // Arrange - var filter = new NoRestoreFilter(); - - var context = new DotNetWatchContext - { - Iteration = 0, - ProcessSpec = new ProcessSpec - { - Arguments = new[] { "run", "-f", "net6.0", "--", "foo=bar" }, - } - }; - await filter.ProcessAsync(context, default); - - context.ChangedFile = new FileItem("Program.cs"); - context.Iteration++; - - // Act - await filter.ProcessAsync(context, default); - - // Assert - Assert.Equal(new[] { "run", "--no-restore", "-f", "net6.0", "--", "foo=bar" }, context.ProcessSpec.Arguments); - } - - [Fact] - public async Task ProcessAsync_AddsNoRestoreSwitch_ForTestCommand() - { - // Arrange - var filter = new NoRestoreFilter(); - - var context = new DotNetWatchContext - { - Iteration = 0, - ProcessSpec = new ProcessSpec - { - Arguments = new[] { "test", "--filter SomeFilter" }, - } - }; - await filter.ProcessAsync(context, default); - - context.ChangedFile = new FileItem("Program.cs"); - context.Iteration++; - - // Act - await filter.ProcessAsync(context, default); - - // Assert - Assert.Equal(new[] { "test", "--no-restore", "--filter SomeFilter" }, context.ProcessSpec.Arguments); - } - - [Fact] - public async Task ProcessAsync_DoesNotModifyArgumentsForUnknownCommands() - { - // Arrange - var filter = new NoRestoreFilter(); - var arguments = new[] { "ef", "database", "update" }; - - var context = new DotNetWatchContext - { - Iteration = 0, - ProcessSpec = new ProcessSpec - { - Arguments = arguments, - } - }; - await filter.ProcessAsync(context, default); - - context.ChangedFile = new FileItem("Program.cs"); - context.Iteration++; - - // Act - await filter.ProcessAsync(context, default); - - // Assert - Assert.Same(arguments, context.ProcessSpec.Arguments); - } - } -} diff --git a/src/Tools/dotnet-watch/test/ProgramTests.cs b/src/Tools/dotnet-watch/test/ProgramTests.cs deleted file mode 100644 index d93c162aec44..000000000000 --- a/src/Tools/dotnet-watch/test/ProgramTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing; -using Microsoft.Extensions.Tools.Internal; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.Tests -{ - public class ProgramTests : IDisposable - { - private readonly TemporaryDirectory _tempDir; - private readonly TestConsole _console; - - public ProgramTests(ITestOutputHelper output) - { - _tempDir = new TemporaryDirectory(); - _console = new TestConsole(output); - } - - [Fact] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23394")] // Failure on OSX https://dev.azure.com/dnceng/public/_build/results?buildId=706059&view=ms.vss-test-web.build-test-results-tab - public async Task ConsoleCancelKey() - { - _tempDir - .WithCSharpProject("testproj") - .WithTargetFrameworks("net6.0") - .Dir() - .WithFile("Program.cs") - .Create(); - - using (var app = new Program(_console, _tempDir.Root)) - { - var run = app.RunAsync(new[] { "run" }); - - await _console.CancelKeyPressSubscribed.TimeoutAfter(TimeSpan.FromSeconds(30)); - _console.ConsoleCancelKey(); - - var exitCode = await run.TimeoutAfter(TimeSpan.FromSeconds(30)); - - Assert.Contains("Shutdown requested. Press Ctrl+C again to force exit.", _console.GetOutput()); - Assert.Equal(0, exitCode); - } - } - - public void Dispose() - { - _tempDir.Dispose(); - } - } -} diff --git a/src/Tools/dotnet-watch/test/Properties/AssemblyInfo.cs b/src/Tools/dotnet-watch/test/Properties/AssemblyInfo.cs deleted file mode 100644 index 8faa300eb505..000000000000 --- a/src/Tools/dotnet-watch/test/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Testing; -using Xunit; - -[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/src/Tools/dotnet-watch/test/Scenario/ProjectToolScenario.cs b/src/Tools/dotnet-watch/test/Scenario/ProjectToolScenario.cs deleted file mode 100644 index 54a90e155855..000000000000 --- a/src/Tools/dotnet-watch/test/Scenario/ProjectToolScenario.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.CommandLineUtils; -using Microsoft.Extensions.Internal; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests -{ - public class ProjectToolScenario : IDisposable - { - private static readonly string TestProjectSourceRoot = Path.Combine(AppContext.BaseDirectory, "TestProjects"); - private readonly ITestOutputHelper? _logger; - - public ProjectToolScenario() - : this(null) - { - } - - public ProjectToolScenario(ITestOutputHelper? logger) - { - WorkFolder = Path.Combine(AppContext.BaseDirectory, "tmp", Path.GetRandomFileName()); - DotNetWatchPath = Path.Combine(AppContext.BaseDirectory, "tool", "dotnet-watch.dll"); - - _logger = logger; - _logger?.WriteLine($"The temporary test folder is {WorkFolder}"); - - CreateTestDirectory(); - } - - public string WorkFolder { get; } - - public string DotNetWatchPath { get; } - - public void AddTestProjectFolder(string projectName) - { - var srcFolder = Path.Combine(TestProjectSourceRoot, projectName); - var destinationFolder = Path.Combine(WorkFolder, Path.GetFileName(projectName)); - _logger?.WriteLine($"Copying project {srcFolder} to {destinationFolder}"); - - Directory.CreateDirectory(destinationFolder); - - foreach (var directory in Directory.GetDirectories(srcFolder, "*", SearchOption.AllDirectories)) - { - Directory.CreateDirectory(directory.Replace(srcFolder, destinationFolder)); - } - - foreach (var file in Directory.GetFiles(srcFolder, "*", SearchOption.AllDirectories)) - { - File.Copy(file, file.Replace(srcFolder, destinationFolder), true); - } - } - - public Task RestoreAsync(string project) - { - _logger?.WriteLine($"Restoring msbuild project in {project}"); - return ExecuteCommandAsync(project, TimeSpan.FromSeconds(120), "restore", "--ignore-failed-sources"); - } - - public Task BuildAsync(string project) - { - _logger?.WriteLine($"Building {project}"); - return ExecuteCommandAsync(project, TimeSpan.FromSeconds(60), "build"); - } - - private async Task ExecuteCommandAsync(string project, TimeSpan timeout, params string[] arguments) - { - var tcs = new TaskCompletionSource(); - project = Path.Combine(WorkFolder, project); - _logger?.WriteLine($"Project directory: '{project}'"); - - var process = new Process - { - EnableRaisingEvents = true, - StartInfo = new ProcessStartInfo - { - FileName = DotNetMuxer.MuxerPathOrDefault(), - Arguments = ArgumentEscaper.EscapeAndConcatenate(arguments), - WorkingDirectory = project, - RedirectStandardOutput = true, - RedirectStandardError = true, - Environment = - { - ["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "true" - } - }, - }; - - void OnData(object sender, DataReceivedEventArgs args) - => _logger?.WriteLine(args.Data ?? string.Empty); - - void OnExit(object? sender, EventArgs args) - { - _logger?.WriteLine($"Process exited {process.Id}"); - tcs.TrySetResult(); - } - - process.ErrorDataReceived += OnData; - process.OutputDataReceived += OnData; - process.Exited += OnExit; - - process.Start(); - - process.BeginErrorReadLine(); - process.BeginOutputReadLine(); - - _logger?.WriteLine($"Started process {process.Id}: {process.StartInfo.FileName} {process.StartInfo.Arguments}"); - - var done = await Task.WhenAny(tcs.Task, Task.Delay(timeout)); - process.CancelErrorRead(); - process.CancelOutputRead(); - - process.ErrorDataReceived -= OnData; - process.OutputDataReceived -= OnData; - process.Exited -= OnExit; - - if (!ReferenceEquals(done, tcs.Task)) - { - if (!process.HasExited) - { - _logger?.WriteLine($"Killing process {process.Id}"); - process.KillTree(); - } - - throw new TimeoutException($"Process timed out after {timeout.TotalSeconds} seconds"); - } - - _logger?.WriteLine($"Process exited {process.Id} with code {process.ExitCode}"); - if (process.ExitCode != 0) - { - - throw new InvalidOperationException($"Exit code {process.ExitCode}"); - } - } - - private void CreateTestDirectory() - { - Directory.CreateDirectory(WorkFolder); - - // On Helix, the Directory.Build.* files already exist in a parent folder. - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) - { - var artifactsBinDirectory = typeof(ProjectToolScenario) - .Assembly - .GetCustomAttributes() - .Single(s => s.Key == "ArtifactsBinDir") - .Value!; - var directoryBuildFilesDirectory = Path.Combine(artifactsBinDirectory, "GenerateFiles"); - - foreach (var filename in new[] {"Directory.Build.props", "Directory.Build.targets"}) - { - File.Copy( - Path.Combine(directoryBuildFilesDirectory, filename), - Path.Combine(WorkFolder, filename), - overwrite: true); - } - } - } - - public void Dispose() - { - try - { - Directory.Delete(WorkFolder, recursive: true); - } - catch - { - Console.WriteLine($"Failed to delete {WorkFolder}. Retrying..."); - Thread.Sleep(TimeSpan.FromSeconds(5)); - Directory.Delete(WorkFolder, recursive: true); - } - } - } -} diff --git a/src/Tools/dotnet-watch/test/Scenario/WatchableApp.cs b/src/Tools/dotnet-watch/test/Scenario/WatchableApp.cs deleted file mode 100644 index ac751a767ba2..000000000000 --- a/src/Tools/dotnet-watch/test/Scenario/WatchableApp.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests -{ - public class WatchableApp : IDisposable - { - private static readonly TimeSpan DefaultMessageTimeOut = TimeSpan.FromSeconds(30); - - private const string StartedMessage = "Started"; - private const string ExitingMessage = "Exiting"; - private const string WatchExitedMessage = "watch : Exited"; - private const string WaitingForFileChangeMessage = "watch : Waiting for a file to change"; - - private readonly ITestOutputHelper _logger; - private string _appName; - private bool _prepared; - - public WatchableApp(string appName, ITestOutputHelper logger) - { - _logger = logger; - _appName = appName; - Scenario = new ProjectToolScenario(logger); - Scenario.AddTestProjectFolder(appName); - SourceDirectory = Path.Combine(Scenario.WorkFolder, appName); - } - - public ProjectToolScenario Scenario { get; } - - public AwaitableProcess? Process { get; protected set; } - - public List DotnetWatchArgs { get; } = new List(); - - public Dictionary EnvironmentVariables { get; } = new Dictionary(); - - public string SourceDirectory { get; } - - public Task HasRestarted() - => HasRestarted(DefaultMessageTimeOut); - - public Task HasRestarted(TimeSpan timeout) - => Process!.GetOutputLineAsync(StartedMessage, timeout); - - public async Task HasExited() - { - await Process!.GetOutputLineAsync(ExitingMessage, DefaultMessageTimeOut); - await Process.GetOutputLineStartsWithAsync(WatchExitedMessage, DefaultMessageTimeOut); - } - - public Task IsWaitingForFileChange() - { - return Process!.GetOutputLineStartsWithAsync(WaitingForFileChangeMessage, DefaultMessageTimeOut); - } - - public bool UsePollingWatcher { get; set; } - - public async Task GetProcessIdentifier() - { - // Process ID is insufficient because PID's may be reused. Process identifier also includes other info to distinguish - // between different process instances. - var line = await Process!.GetOutputLineStartsWithAsync("Process identifier =", DefaultMessageTimeOut); - return line.Split('=').Last(); - } - - public async Task PrepareAsync() - { - await Scenario.RestoreAsync(_appName); - await Scenario.BuildAsync(_appName); - _prepared = true; - } - - [MemberNotNull(nameof(Process))] - public void Start(IEnumerable arguments, [CallerMemberName] string name = null) - { - if (!_prepared) - { - throw new InvalidOperationException($"Call {nameof(PrepareAsync)} first"); - } - - var args = new List - { - Scenario.DotNetWatchPath, - }; - args.AddRange(DotnetWatchArgs); - args.AddRange(arguments); - - var dotnetPath = "dotnet"; - - // Fallback to embedded path to dotnet when not on helix - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) - { - dotnetPath = typeof(WatchableApp).Assembly.GetCustomAttributes() - .Single(s => s.Key == "DotnetPath").Value!; - } - - var spec = new ProcessSpec - { - Executable = dotnetPath, - Arguments = args, - WorkingDirectory = SourceDirectory, - EnvironmentVariables = - { - ["DOTNET_USE_POLLING_FILE_WATCHER"] = UsePollingWatcher.ToString(), - ["__DOTNET_WATCH_RUNNING_AS_TEST"] = "true", - }, - }; - - foreach (var env in EnvironmentVariables) - { - spec.EnvironmentVariables.Add(env.Key, env.Value); - } - - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) - { - spec.EnvironmentVariables["DOTNET_ROOT"] = Directory.GetParent(dotnetPath)!.FullName; - } - - Process = new AwaitableProcess(spec, _logger); - Process.Start(); - } - - [MemberNotNull(nameof(Process))] - public Task StartWatcherAsync([CallerMemberName] string name = null) - => StartWatcherAsync(Array.Empty(), name); - - [MemberNotNull(nameof(Process))] - public async Task StartWatcherAsync(string[] arguments, [CallerMemberName] string name = null) - { - if (!_prepared) - { -#pragma warning disable CS8774 // Member must have a non-null value when exiting. - await PrepareAsync(); -#pragma warning restore CS8774 // Member must have a non-null value when exiting. - } - - var args = new[] { "run", "--" }.Concat(arguments); - Start(args, name); - - // Make this timeout long because it depends much on the MSBuild compilation speed. - // Slow machines may take a bit to compile and boot test apps - await Process.GetOutputLineAsync(StartedMessage, TimeSpan.FromMinutes(2)); - } - - public virtual void Dispose() - { - _logger?.WriteLine("Disposing WatchableApp"); - Process?.Dispose(); - Scenario?.Dispose(); - } - } -} diff --git a/src/Tools/dotnet-watch/test/StaticContentHandlerTest.cs b/src/Tools/dotnet-watch/test/StaticContentHandlerTest.cs deleted file mode 100644 index 756d5e673c2e..000000000000 --- a/src/Tools/dotnet-watch/test/StaticContentHandlerTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Tools.Internal; -using Moq; -using Xunit; - -namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests -{ - public class StaticContentHandlerTest - { - [Fact] - public async ValueTask TryHandleFileAction_WritesUpdateCssMessage() - { - // Arrange - var server = new Mock(NullReporter.Singleton); - byte[]? writtenBytes = null; - server.Setup(s => s.SendMessage(It.IsAny(), It.IsAny())) - .Callback((byte[] bytes, CancellationToken cts) => - { - writtenBytes = bytes; - }); - - // Act - await StaticContentHandler.TryHandleFileAction(server.Object, new FileItem("Test.css", FileKind.StaticFile, "content/Test.css"), default); - - // Assert - Assert.NotNull(writtenBytes); - var writtenString = Encoding.UTF8.GetString(writtenBytes!); - Assert.Equal("UpdateCSS||content/Test.css", writtenString); - } - - [Fact] - public async ValueTask TryHandleFileAction_CausesBrowserRefreshForNonCssFile() - { - // Arrange - var server = new Mock(NullReporter.Singleton); - byte[]? writtenBytes = null; - server.Setup(s => s.SendMessage(It.IsAny(), It.IsAny())) - .Callback((byte[] bytes, CancellationToken cts) => - { - writtenBytes = bytes; - }); - - // Act - await StaticContentHandler.TryHandleFileAction(server.Object, new FileItem("Test.js", FileKind.StaticFile, "content/Test.js"), default); - - // Assert - Assert.NotNull(writtenBytes); - var writtenString = Encoding.UTF8.GetString(writtenBytes!); - Assert.Equal("Reload", writtenString); - } - } -} diff --git a/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj b/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj deleted file mode 100644 index 765e01c2d877..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/AppWithDeps.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - $(DefaultNetCoreTargetFramework) - exe - true - - - - - - - diff --git a/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/Program.cs b/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/Program.cs deleted file mode 100644 index a96a0bf34156..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/AppWithDeps/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Threading; - -namespace ConsoleApplication -{ - public class Program - { - private static readonly int processId = Process.GetCurrentProcess().Id; - - public static void Main(string[] args) - { - Console.WriteLine("Started"); - // Process ID is insufficient because PID's may be reused. - Console.WriteLine($"Process identifier = {Process.GetCurrentProcess().Id}, {Process.GetCurrentProcess().StartTime:hh:mm:ss.FF}"); - Thread.Sleep(Timeout.Infinite); - } - } -} diff --git a/src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/AppWithLaunchSettings.csproj b/src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/AppWithLaunchSettings.csproj deleted file mode 100644 index 1ece3a88d85d..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/AppWithLaunchSettings.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - $(DefaultNetCoreTargetFramework) - true - Exe - - - diff --git a/src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/Program.cs b/src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/Program.cs deleted file mode 100644 index fb36946480b3..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/Program.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; - -namespace ConsoleApplication -{ - public class Program - { - public static void Main(string[] args) - { - Console.WriteLine("Started"); - - // Simulate an ASP.NET Core app. We want to avoid referencing asp.net core from a test asset - Console.WriteLine("info: Microsoft.Hosting.Lifetime[0]"); - Console.WriteLine(" Now listening on: https://localhost:5001"); - Console.WriteLine("info: Microsoft.Hosting.Lifetime[0]"); - Console.WriteLine(" Now listening on: http://localhost:5000"); - Console.WriteLine("info: Microsoft.Hosting.Lifetime[0]"); - Console.WriteLine(" Application started. Press Ctrl+C to shut down."); - Console.WriteLine("info: Microsoft.Hosting.Lifetime[0]"); - Console.WriteLine(" Hosting environment: Development"); - Console.WriteLine("info: Microsoft.Hosting.Lifetime[0]"); - Console.WriteLine($" Content root path: {Directory.GetCurrentDirectory()}"); - - Thread.Sleep(Timeout.Infinite); - } - } -} diff --git a/src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/Properties/launchSettings.json b/src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/Properties/launchSettings.json deleted file mode 100644 index 5fd9c9bd8689..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/AppWithLaunchSettings/Properties/launchSettings.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:38420", - "sslPort": 44393 - } - }, - "profiles": { - "b3": { - "commandName": "Project", - "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "weatherforecast", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/src/Tools/dotnet-watch/test/TestProjects/Dependency/Dependency.csproj b/src/Tools/dotnet-watch/test/TestProjects/Dependency/Dependency.csproj deleted file mode 100644 index 0dbf97b94450..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/Dependency/Dependency.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - netstandard2.0 - true - - - diff --git a/src/Tools/dotnet-watch/test/TestProjects/Dependency/Foo.cs b/src/Tools/dotnet-watch/test/TestProjects/Dependency/Foo.cs deleted file mode 100644 index 3441304e2fde..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/Dependency/Foo.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Dependency -{ - public class Foo - { - } -} diff --git a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj b/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj deleted file mode 100644 index ada8a9974414..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/GlobbingApp.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - $(DefaultNetCoreTargetFramework) - exe - false - true - - - - - - - diff --git a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/Program.cs b/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/Program.cs deleted file mode 100644 index 6d8e28e6f780..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/Program.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Threading; - -namespace ConsoleApplication -{ - public class Program - { - public static void Main(string[] args) - { - Console.WriteLine("Started"); - // Process ID is insufficient because PID's may be reused. - Console.WriteLine($"Process identifier = {Process.GetCurrentProcess().Id}, {Process.GetCurrentProcess().StartTime:hh:mm:ss.FF}"); - Console.WriteLine("Defined types = " + typeof(Program).Assembly.DefinedTypes.Count()); - Thread.Sleep(Timeout.Infinite); - } - } -} diff --git a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/exclude/Baz.cs b/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/exclude/Baz.cs deleted file mode 100644 index fdaebd72010b..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/exclude/Baz.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace GlobbingApp.exclude -{ - public class Baz - { - "THIS FILE SHOULD NOT BE INCLUDED IN COMPILATION" - } -} diff --git a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/include/Foo.cs b/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/include/Foo.cs deleted file mode 100644 index d1afb658fc61..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/GlobbingApp/include/Foo.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace GlobbingApp.include -{ - public class Foo - { - } -} diff --git a/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/.gitignore b/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/.gitignore deleted file mode 100644 index ab929c6551ce..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.net \ No newline at end of file diff --git a/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj b/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj deleted file mode 100644 index 73474cccd790..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/KitchenSink.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - .net/obj - .net/bin - - - - - - Exe - $(DefaultNetCoreTargetFramework) - true - - - - - diff --git a/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/Program.cs b/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/Program.cs deleted file mode 100644 index 329c4930a260..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/KitchenSink/Program.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Threading; - -namespace KitchenSink -{ - class Program - { - static void Main(string[] args) - { - Console.WriteLine("Started"); - // Process ID is insufficient because PID's may be reused. - Console.WriteLine($"Process identifier = {Process.GetCurrentProcess().Id}, {Process.GetCurrentProcess().StartTime:hh:mm:ss.FF}"); - Console.WriteLine("DOTNET_WATCH = " + Environment.GetEnvironmentVariable("DOTNET_WATCH")); - Console.WriteLine("DOTNET_WATCH_ITERATION = " + Environment.GetEnvironmentVariable("DOTNET_WATCH_ITERATION")); - - if (args.Length > 0 && args[0] == "wait") - { - Console.WriteLine("Waiting for process to be terminated."); - Thread.Sleep(Timeout.Infinite); - } - } - } -} diff --git a/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj b/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj deleted file mode 100644 index 8d8abf6e97c9..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/NoDepsApp.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - $(DefaultNetCoreTargetFramework) - exe - true - - - diff --git a/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/Program.cs b/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/Program.cs deleted file mode 100644 index f3da51ab089b..000000000000 --- a/src/Tools/dotnet-watch/test/TestProjects/NoDepsApp/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Threading; - -namespace ConsoleApplication -{ - public class Program - { - public static void Main(string[] args) - { - Console.WriteLine("Started"); - // Process ID is insufficient because PID's may be reused. - Console.WriteLine($"Process identifier = {Process.GetCurrentProcess().Id}, {Process.GetCurrentProcess().StartTime:hh:mm:ss.FF}"); - if (args.Length > 0 && args[0] == "--no-exit") - { - Thread.Sleep(Timeout.Infinite); - } - Console.WriteLine("Exiting"); - } - } -} diff --git a/src/Tools/dotnet-watch/test/Utilities/TestProjectGraph.cs b/src/Tools/dotnet-watch/test/Utilities/TestProjectGraph.cs deleted file mode 100644 index 87bd4bcf9e96..000000000000 --- a/src/Tools/dotnet-watch/test/Utilities/TestProjectGraph.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using Microsoft.Extensions.Tools.Internal; - -namespace Microsoft.DotNet.Watcher.Tools.Tests -{ - public class TestProjectGraph - { - private readonly TemporaryDirectory _directory; - private Action? _onCreate; - private readonly Dictionary _projects = new Dictionary(); - public TestProjectGraph(TemporaryDirectory directory) - { - _directory = directory; - } - - public void OnCreate(Action onCreate) - { - _onCreate = onCreate; - } - - public TemporaryCSharpProject? Find(string projectName) - => _projects.ContainsKey(projectName) - ? _projects[projectName] - : null; - - public TemporaryCSharpProject GetOrCreate(string projectName) - { - if (!_projects.TryGetValue(projectName, out var sourceProj)) - { - sourceProj = _directory.SubDir(projectName).WithCSharpProject(projectName); - _onCreate?.Invoke(sourceProj); - _projects.Add(projectName, sourceProj); - } - return sourceProj; - } - } -} diff --git a/src/Tools/dotnet-watch/test/dotnet-watch.Tests.csproj b/src/Tools/dotnet-watch/test/dotnet-watch.Tests.csproj deleted file mode 100644 index 528ae37a96f3..000000000000 --- a/src/Tools/dotnet-watch/test/dotnet-watch.Tests.csproj +++ /dev/null @@ -1,69 +0,0 @@ - - - - $(DefaultNetCoreTargetFramework) - Microsoft.DotNet.Watcher.Tools.Tests - $(DefaultItemExcludes);TestProjects\**\* - DotNetWatcherToolsTests - true - enable - - - - - - - - - - - - - - - - - - - - - <_Parameter1>ArtifactsBinDir - <_Parameter2>$(ArtifactsBinDir) - - - <_Parameter1>DotnetPath - <_Parameter2>$(DotNetTool) - - - - - - - - - - - - - - - - - - - - - -