diff --git a/NuGet.config b/NuGet.config
index a419e73e558e..419b8d3afd2c 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -4,12 +4,13 @@
-
-
+
+
+
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 97a78c943536..6b98b1565141 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -15,42 +15,42 @@
a23da1c15c737b5e121650cfa5a86805e74e34fc
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
@@ -59,154 +59,154 @@
https://github.com/dotnet/core-setup
7d57652f33493fa022125b7f63aad0d70c52d810
-
+
https://github.com/dotnet/emsdk
- 6e079c23aee94577c17bae34522b52df0d646ca5
+ 8e660ff41e91879977e3a9d837e068bd72234c26
-
+
https://github.com/dotnet/emsdk
- 6e079c23aee94577c17bae34522b52df0d646ca5
+ 8e660ff41e91879977e3a9d837e068bd72234c26
-
+
https://github.com/dotnet/msbuild
- 988196b1c64418351f46da7d3a9ff403560a4270
+ 95c7bf011ac3b849c63cfaf5584878232d223b71
-
+
https://github.com/dotnet/msbuild
- 988196b1c64418351f46da7d3a9ff403560a4270
+ 95c7bf011ac3b849c63cfaf5584878232d223b71
-
+
https://github.com/dotnet/msbuild
- 988196b1c64418351f46da7d3a9ff403560a4270
+ 95c7bf011ac3b849c63cfaf5584878232d223b71
-
+
https://github.com/dotnet/fsharp
- 3044166cd923167204853d1d9f975bc26864f86f
+ 6b652cd1d5c02d6f331818ef16479499161b5b6d
-
+
https://github.com/dotnet/fsharp
- 3044166cd923167204853d1d9f975bc26864f86f
+ 6b652cd1d5c02d6f331818ef16479499161b5b6d
-
+
https://github.com/dotnet/roslyn
- 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9
+ ba2f19abe63a267dc9b2d082d0e1904cd91c1947
-
+
https://github.com/dotnet/roslyn
- 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9
+ ba2f19abe63a267dc9b2d082d0e1904cd91c1947
-
+
https://github.com/dotnet/roslyn
- 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9
+ ba2f19abe63a267dc9b2d082d0e1904cd91c1947
-
+
https://github.com/dotnet/roslyn
- 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9
+ ba2f19abe63a267dc9b2d082d0e1904cd91c1947
-
+
https://github.com/dotnet/roslyn
- 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9
+ ba2f19abe63a267dc9b2d082d0e1904cd91c1947
-
+
https://github.com/dotnet/roslyn
- 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9
+ ba2f19abe63a267dc9b2d082d0e1904cd91c1947
-
+
https://github.com/dotnet/roslyn
- 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9
+ ba2f19abe63a267dc9b2d082d0e1904cd91c1947
-
+
https://github.com/dotnet/roslyn
- 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9
+ ba2f19abe63a267dc9b2d082d0e1904cd91c1947
-
+
https://github.com/dotnet/roslyn
- 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9
+ ba2f19abe63a267dc9b2d082d0e1904cd91c1947
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
-
+
https://github.com/nuget/nuget.client
- e082545cbba30ec8dc9b759f2d9beea8aabc6215
+ 675e8a81acb53958e40dc21be5f562d00239a449
https://github.com/microsoft/vstest
@@ -226,134 +226,134 @@
07acde22b65497e72de145d57167b83609a7f7fb
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/windowsdesktop
- f37a996ae93c3aaad652c36d43a6245ad090d775
+ bb957f983a1377b7b9e25f2d24539ec4ab890fcc
-
+
https://github.com/dotnet/windowsdesktop
- f37a996ae93c3aaad652c36d43a6245ad090d775
+ bb957f983a1377b7b9e25f2d24539ec4ab890fcc
-
+
https://github.com/dotnet/windowsdesktop
- f37a996ae93c3aaad652c36d43a6245ad090d775
+ bb957f983a1377b7b9e25f2d24539ec4ab890fcc
-
+
https://github.com/dotnet/windowsdesktop
- f37a996ae93c3aaad652c36d43a6245ad090d775
+ bb957f983a1377b7b9e25f2d24539ec4ab890fcc
-
+
https://github.com/dotnet/wpf
- d8b93d9e0c5ca81cfae3ff691824c3cdef74f5b3
+ 32546f1aeee78cb85fe80191126ea3ecbe47aeed
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/razor
- 4b3e4096e8b7aa647340e9f1cc21720741c35f86
+ 4b1b541ed34e34b4b91202871f661879dabb4f55
-
+
https://github.com/dotnet/razor
- 4b3e4096e8b7aa647340e9f1cc21720741c35f86
+ 4b1b541ed34e34b4b91202871f661879dabb4f55
-
+
https://github.com/dotnet/razor
- 4b3e4096e8b7aa647340e9f1cc21720741c35f86
+ 4b1b541ed34e34b4b91202871f661879dabb4f55
-
+
https://github.com/dotnet/razor
- 4b3e4096e8b7aa647340e9f1cc21720741c35f86
+ 4b1b541ed34e34b4b91202871f661879dabb4f55
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
https://github.com/dotnet/test-templates
@@ -375,47 +375,47 @@
https://github.com/dotnet/test-templates
49c9ad01f057b3c6352bbec12b117acc2224493c
-
+
https://github.com/dotnet/test-templates
- 2b8a55f3d3f380ae10d2c2f4d0974c92727ccc22
+ 950cc4a13d0c3f7d84558aec0552e15a5e8559d4
-
+
https://github.com/dotnet/test-templates
- 2b8a55f3d3f380ae10d2c2f4d0974c92727ccc22
+ 950cc4a13d0c3f7d84558aec0552e15a5e8559d4
-
+
https://github.com/dotnet/winforms
- f1676f5dcec5d4a84228406113fcad5fe31025af
+ 0749a97fb592ed4a5d7ddc8d13acf9f5097f68f7
-
+
https://github.com/dotnet/wpf
- d8b93d9e0c5ca81cfae3ff691824c3cdef74f5b3
+ 32546f1aeee78cb85fe80191126ea3ecbe47aeed
-
+
https://github.com/dotnet/xdt
- c2a9df9c1867454039a1223cef1c090359e33646
+ 4ddd8113a29852380b7b929117bfe67f401ac320
-
+
https://github.com/dotnet/xdt
- c2a9df9c1867454039a1223cef1c090359e33646
+ 4ddd8113a29852380b7b929117bfe67f401ac320
-
+
https://github.com/dotnet/roslyn-analyzers
- a7c74cf887abe4a38240bc4ead0b221d9d42434f
+ 2595aeb5e9a506f3f845c01be18d70ded045e33a
-
+
https://github.com/dotnet/roslyn-analyzers
- a7c74cf887abe4a38240bc4ead0b221d9d42434f
+ 2595aeb5e9a506f3f845c01be18d70ded045e33a
-
+
https://github.com/dotnet/roslyn-analyzers
- a7c74cf887abe4a38240bc4ead0b221d9d42434f
+ 2595aeb5e9a506f3f845c01be18d70ded045e33a
@@ -441,49 +441,49 @@
-
+
https://github.com/dotnet/source-build-externals
- e4ddbedd151b969514f2c5b756616707c31e0bfb
+ 4e60131607fd144eb86fe4487f1a37da940ca990
-
+
https://github.com/dotnet/source-build-reference-packages
- 46174fbca16412ddabc1e881f6281192924e4ed3
+ 6a1e86367923914589bda2244eb2321b97523c33
https://github.com/dotnet/deployment-tools
7871ee378dce87b64d930d4f33dca9c888f4034d
-
+
https://github.com/dotnet/sourcelink
- 1099a677155d9d4c1a81a612dffccbcf0d94f8e9
+ 9700a2bfe76f993dbeb34439d4fe320c31182b2a
-
+
https://github.com/dotnet/sourcelink
- 1099a677155d9d4c1a81a612dffccbcf0d94f8e9
+ 9700a2bfe76f993dbeb34439d4fe320c31182b2a
-
+
https://github.com/dotnet/sourcelink
- 1099a677155d9d4c1a81a612dffccbcf0d94f8e9
+ 9700a2bfe76f993dbeb34439d4fe320c31182b2a
-
+
https://github.com/dotnet/sourcelink
- 1099a677155d9d4c1a81a612dffccbcf0d94f8e9
+ 9700a2bfe76f993dbeb34439d4fe320c31182b2a
-
+
https://github.com/dotnet/sourcelink
- 1099a677155d9d4c1a81a612dffccbcf0d94f8e9
+ 9700a2bfe76f993dbeb34439d4fe320c31182b2a
-
+
https://github.com/dotnet/sourcelink
- 1099a677155d9d4c1a81a612dffccbcf0d94f8e9
+ 9700a2bfe76f993dbeb34439d4fe320c31182b2a
-
+
https://github.com/dotnet/sourcelink
- 1099a677155d9d4c1a81a612dffccbcf0d94f8e9
+ 9700a2bfe76f993dbeb34439d4fe320c31182b2a
@@ -499,125 +499,125 @@
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/aspnetcore
- 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819
+ 9a34a6e3c7975f41300bd2550a089a85810cafd1
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
-
+
https://github.com/dotnet/arcade
- 04b9022eba9c184a8036328af513c22e6949e8b6
+ 1230437de1ab7b3e15fe7cdfe7ffce2f65449959
-
+
https://github.com/dotnet/arcade
- 04b9022eba9c184a8036328af513c22e6949e8b6
+ 1230437de1ab7b3e15fe7cdfe7ffce2f65449959
-
+
https://github.com/dotnet/arcade
- 04b9022eba9c184a8036328af513c22e6949e8b6
+ 1230437de1ab7b3e15fe7cdfe7ffce2f65449959
-
+
https://github.com/dotnet/arcade
- 04b9022eba9c184a8036328af513c22e6949e8b6
+ 1230437de1ab7b3e15fe7cdfe7ffce2f65449959
-
+
https://github.com/dotnet/arcade
- 04b9022eba9c184a8036328af513c22e6949e8b6
+ 1230437de1ab7b3e15fe7cdfe7ffce2f65449959
-
+
https://github.com/dotnet/arcade
- 04b9022eba9c184a8036328af513c22e6949e8b6
+ 1230437de1ab7b3e15fe7cdfe7ffce2f65449959
-
+
https://github.com/dotnet/arcade
- 04b9022eba9c184a8036328af513c22e6949e8b6
+ 1230437de1ab7b3e15fe7cdfe7ffce2f65449959
-
+
https://github.com/dotnet/runtime
- 46cfb747b4c22471242dee0d106f5c79cf9fd4c5
+ 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815
https://github.com/dotnet/arcade-services
@@ -627,14 +627,14 @@
https://github.com/dotnet/arcade-services
47e3672c762970073e4282bd563233da86bcca3e
-
+
https://github.com/dotnet/scenario-tests
- d92413b87d36250859d8cb51ff69a03b5f5c4cab
+ 76edac963b2c6296018c7bf1e04a03c8488076d3
-
+
https://github.com/dotnet/scenario-tests
- d92413b87d36250859d8cb51ff69a03b5f5c4cab
+ 76edac963b2c6296018c7bf1e04a03c8488076d3
-
+
https://github.com/dotnet/aspire
- 75fdcff28495bdd643f6323133a7d411df71ab70
+ 137e8dcae0a7b22c05f48c4e7a5d36fe3f00a8d7
-
+
https://github.com/dotnet/aspire
- 75fdcff28495bdd643f6323133a7d411df71ab70
+ 137e8dcae0a7b22c05f48c4e7a5d36fe3f00a8d7
diff --git a/eng/Versions.props b/eng/Versions.props
index 71a097b631b7..e10330210c7b 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -19,10 +19,11 @@
false
release
- rc
+ rtm
rtm
servicing
- 2
+
+
@@ -69,7 +70,7 @@
https://dotnetclimsrc.blob.core.windows.net/dotnet/
- 9.0.0-preview.24453.1
+ 9.0.0-preview.24476.1
1.0.0-20230414.1
2.22.0
2.0.1-servicing-26011-01
@@ -111,80 +112,80 @@
- 9.0.0-rc.2.24466.1
+ 9.0.0-rtm.24474.4
1.1.0-rc.24069.1
1.1.0-rc.24202.1
- 1.1.0-rc.24463.2
+ 1.1.0-rc.24477.5
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
8.0.0-rc.1.23414.4
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
2.1.0
- 9.0.0-rc.2.24463.7
+ 9.0.0-rtm.24476.4
8.0.0
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
8.0.0
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
8.0.4
- 9.0.0-rc.2.24463.7
- 9.0.0-rc.2.24463.7
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
- 9.0.0-rtm.24467.8
- 9.0.0-rtm.24467.8
- 9.0.0-rtm.24467.8
- 9.0.0-rtm.24467.8
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
+ 9.0.0-rtm.24476.4
- 6.12.0-rc.108
- 6.12.0-rc.108
- 6.12.0-rc.108
- 6.12.0-rc.108
- 6.12.0-rc.108
- 6.12.0-rc.108
- 6.12.0-rc.108
- 6.12.0-rc.108
- 6.12.0-rc.108
- 6.12.0-rc.108
- 6.12.0-rc.108
- 6.12.0-rc.108
+ 6.12.0-rc.120
+ 6.12.0-rc.120
+ 6.12.0-rc.120
+ 6.12.0-rc.120
+ 6.12.0-rc.120
+ 6.12.0-rc.120
+ 6.12.0-rc.120
+ 6.12.0-rc.120
+ 6.12.0-rc.120
+ 6.12.0-rc.120
+ 6.12.0-rc.120
+ 6.12.0-rc.120
@@ -194,8 +195,8 @@
- 9.0.0-preview.24454.1
- 3.11.0-beta1.24454.1
+ 9.0.0-preview.24479.1
+ 3.11.0-beta1.24479.1
@@ -212,8 +213,8 @@
then use that in Directory.Packages.props.
At usage sites, either we use MicrosoftBuildMinimumVersion, or MicrosoftBuildVersion in source-only modes. -->
- 17.12.0-preview-24467-02
- 17.12.0-preview-24467-02
+ 17.12.0
+ 17.12.0-preview-24477-01
$([System.IO.File]::ReadAllText('$(RepoRoot)src\Layout\redist\minimumMSBuildVersion').Trim())
@@ -231,45 +232,45 @@
- 12.9.100-beta.24466.6
+ 12.9.100-beta.24477.3
- 4.12.0-3.24463.1
- 4.12.0-3.24463.1
- 4.12.0-3.24463.1
- 4.12.0-3.24463.1
- 4.12.0-3.24463.1
- 4.12.0-3.24463.1
- 4.12.0-3.24463.1
- 4.12.0-3.24463.1
+ 4.12.0-3.24479.1
+ 4.12.0-3.24479.1
+ 4.12.0-3.24479.1
+ 4.12.0-3.24479.1
+ 4.12.0-3.24479.1
+ 4.12.0-3.24479.1
+ 4.12.0-3.24479.1
+ 4.12.0-3.24479.1
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
- 9.0.0-rtm.24466.12
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
+ 9.0.0-rtm.24477.5
- 9.0.0-preview.24467.4
- 9.0.0-preview.24467.4
- 9.0.0-preview.24467.4
+ 9.0.0-preview.24476.3
+ 9.0.0-preview.24476.3
+ 9.0.0-preview.24476.3
- 9.0.0-rtm.24467.10
- 9.0.0-rtm.24467.10
+ 9.0.0-rtm.24476.2
+ 9.0.0-rtm.24476.2
@@ -304,7 +305,7 @@
2.2.0-beta.19072.10
2.0.0
- 9.0.0-preview.24372.1
+ 9.0.0-preview.24475.2
@@ -313,19 +314,19 @@
- 9.0.0-beta.24466.2
- 9.0.0-beta.24466.2
- 9.0.0-beta.24466.2
- 9.0.0-beta.24466.2
+ 9.0.0-beta.24473.1
+ 9.0.0-beta.24473.1
+ 9.0.0-beta.24473.1
+ 9.0.0-beta.24473.1
- 9.0.0-beta.24466.3
- 9.0.0-beta.24466.3
- 9.0.0-beta.24466.3
- 9.0.0-beta.24466.3
- 9.0.0-beta.24466.3
- 9.0.0-beta.24466.3
+ 9.0.0-beta.24474.2
+ 9.0.0-beta.24474.2
+ 9.0.0-beta.24474.2
+ 9.0.0-beta.24474.2
+ 9.0.0-beta.24474.2
+ 9.0.0-beta.24474.2
@@ -351,7 +352,7 @@
8.0.100
- 8.2.0
+ 8.2.1
9.0.100-preview.6
9.0.0-preview.6.24327.7
34.99.0-preview.6.340
@@ -360,14 +361,14 @@
14.2.9714-net9-p6
17.2.9714-net9-p6
- 9.0.0-rc.2.24455.1
+ 9.0.0-rtm.24469.1
$(MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportPackageVersion)
- 9.0.100$([System.Text.RegularExpressions.Regex]::Match($(EmscriptenWorkloadManifestVersion), `-[A-z]*[\.]*\d*`))
+ 9.0.100$([System.Text.RegularExpressions.Regex]::Match($(EmscriptenWorkloadManifestVersion), `-(?!rtm)[A-z]*[\.]*\d*`))
$(MicrosoftNETCoreAppRefPackageVersion)
- 9.0.100$([System.Text.RegularExpressions.Regex]::Match($(MonoWorkloadManifestVersion), `-[A-z]*[\.]*\d*`))
+ 9.0.100$([System.Text.RegularExpressions.Regex]::Match($(MonoWorkloadManifestVersion), `-(?!rtm)[A-z]*[\.]*\d*`))
diff --git a/global.json b/global.json
index c2dd7c515dd0..3581b41bfa75 100644
--- a/global.json
+++ b/global.json
@@ -17,8 +17,8 @@
"cmake": "latest"
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.24466.2",
- "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.24466.2",
+ "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.24473.1",
+ "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.24473.1",
"Microsoft.Build.NoTargets": "3.7.0",
"Microsoft.DotNet.CMake.Sdk": "9.0.0-beta.24217.1"
}
diff --git a/sdk.sln b/sdk.sln
index c57d23959f8c..1e3a6f52e474 100644
--- a/sdk.sln
+++ b/sdk.sln
@@ -507,6 +507,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.WebTools.AspireSe
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Net.Sdk.Compilers.Toolset", "src\Microsoft.Net.Sdk.Compilers.Toolset\Microsoft.Net.Sdk.Compilers.Toolset.csproj", "{FA579C03-2EB4-4D47-88EE-BFF339E96FAF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.WebTools.AspireService.Tests", "test\Microsoft.WebTools.AspireService.Tests\Microsoft.WebTools.AspireService.Tests.csproj", "{1F0B4B3C-DC88-4740-B04F-1707102E9930}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -965,6 +967,10 @@ Global
{FA579C03-2EB4-4D47-88EE-BFF339E96FAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA579C03-2EB4-4D47-88EE-BFF339E96FAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA579C03-2EB4-4D47-88EE-BFF339E96FAF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1F0B4B3C-DC88-4740-B04F-1707102E9930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1F0B4B3C-DC88-4740-B04F-1707102E9930}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1F0B4B3C-DC88-4740-B04F-1707102E9930}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1F0B4B3C-DC88-4740-B04F-1707102E9930}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1143,6 +1149,7 @@ Global
{19014C60-F87C-4CC7-AC0F-C41B6126EBCE} = {71A9F549-0EB6-41F9-BC16-4A6C5007FC91}
{94C8526E-DCC2-442F-9868-3DD0BA2688BE} = {71A9F549-0EB6-41F9-BC16-4A6C5007FC91}
{FA579C03-2EB4-4D47-88EE-BFF339E96FAF} = {22AB674F-ED91-4FBC-BFEE-8A1E82F9F05E}
+ {1F0B4B3C-DC88-4740-B04F-1707102E9930} = {580D1AE7-AA8F-4912-8B76-105594E00B3B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FB8F26CE-4DE6-433F-B32A-79183020BBD6}
@@ -1150,6 +1157,7 @@ Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompat.Shared\Microsoft.DotNet.ApiCompat.Shared.projitems*{03c5a84a-982b-4f38-ac73-ab832c645c4a}*SharedItemsImports = 5
src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompat.Shared\Microsoft.DotNet.ApiCompat.Shared.projitems*{0a3c9afd-f6e6-4a5d-83fb-93bf66732696}*SharedItemsImports = 5
+ src\BuiltInTools\AspireService\Microsoft.WebTools.AspireService.projitems*{1f0b4b3c-dc88-4740-b04f-1707102e9930}*SharedItemsImports = 5
src\BuiltInTools\AspireService\Microsoft.WebTools.AspireService.projitems*{94c8526e-dcc2-442f-9868-3dd0ba2688be}*SharedItemsImports = 13
src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompat.Shared\Microsoft.DotNet.ApiCompat.Shared.projitems*{9d36039f-d0a1-462f-85b4-81763c6b02cb}*SharedItemsImports = 13
src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompat.Shared\Microsoft.DotNet.ApiCompat.Shared.projitems*{a9103b98-d888-4260-8a05-fa36f640698a}*SharedItemsImports = 5
diff --git a/src/BuiltInTools/AspireService/AspireServerService.cs b/src/BuiltInTools/AspireService/AspireServerService.cs
index d777ce951154..3b620131f73f 100644
--- a/src/BuiltInTools/AspireService/AspireServerService.cs
+++ b/src/BuiltInTools/AspireService/AspireServerService.cs
@@ -1,18 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
using System.Net;
using System.Net.WebSockets;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
-using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
diff --git a/src/BuiltInTools/AspireService/Helpers/CertGenerator.cs b/src/BuiltInTools/AspireService/Helpers/CertGenerator.cs
index 189a88c48ed5..a0b0f7766d48 100644
--- a/src/BuiltInTools/AspireService/Helpers/CertGenerator.cs
+++ b/src/BuiltInTools/AspireService/Helpers/CertGenerator.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
@@ -35,7 +34,11 @@ public static X509Certificate2 GenerateCert()
// The file will be automatically generated by the following call and disposed when the returned cert is disposed.
using (cert)
{
+#if NET9_0_OR_GREATER
+ return X509CertificateLoader.LoadPkcs12(cert.Export(X509ContentType.Pfx), password: null, X509KeyStorageFlags.UserKeySet);
+#else
return new X509Certificate2(cert.Export(X509ContentType.Pfx), "", X509KeyStorageFlags.UserKeySet);
+#endif
}
}
else
diff --git a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs b/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs
index 0941b5b89621..1968f7d21aa3 100644
--- a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs
+++ b/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs
@@ -26,9 +26,11 @@ public static void Initialize()
// When launching the application process dotnet-watch sets Hot Reload environment variables via CLI environment directives (dotnet [env:X=Y] run).
// Currently, the CLI parser sets the env variables to the dotnet.exe process itself, rather then to the target process.
// This may cause the dotnet.exe process to connect to the named pipe and break it for the target process.
- if (Path.ChangeExtension(processPath, ".exe") != Path.ChangeExtension(s_targetProcessPath, ".exe"))
+ var processExe = Path.ChangeExtension(processPath, ".exe");
+ var expectedExe = Path.ChangeExtension(s_targetProcessPath, ".exe");
+ if (!string.Equals(processExe, expectedExe, Path.DirectorySeparatorChar == '\\' ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
{
- Log($"Ignoring process '{processPath}', expecting '{s_targetProcessPath}'");
+ Log($"Ignoring process '{processExe}', expecting '{expectedExe}'");
return;
}
diff --git a/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs b/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs
index 785219c3879e..d2f57ce7eb34 100644
--- a/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs
+++ b/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs
@@ -27,7 +27,7 @@ public override async Task WatchAsync(CancellationToken cancellationToken)
var environmentBuilder = EnvironmentVariablesBuilder.FromCurrentEnvironment();
- FileItem? changedFile = null;
+ ChangedFile? changedFile = null;
var buildEvaluator = new BuildEvaluator(Context, RootFileSetFactory);
await using var browserConnector = new BrowserConnector(Context);
@@ -86,7 +86,7 @@ public override async Task WatchAsync(CancellationToken cancellationToken)
var processTask = ProcessRunner.RunAsync(processSpec, Context.Reporter, isUserApplication: true, processExitedSource: null, combinedCancellationSource.Token);
- Task fileSetTask;
+ Task fileSetTask;
Task finishedTask;
while (true)
@@ -94,9 +94,9 @@ public override async Task WatchAsync(CancellationToken cancellationToken)
fileSetTask = fileSetWatcher.GetChangedFileAsync(startedWatching: null, combinedCancellationSource.Token);
finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task);
- if (staticFileHandler != null && finishedTask == fileSetTask && fileSetTask.Result is FileItem fileItem)
+ if (staticFileHandler != null && finishedTask == fileSetTask && fileSetTask.Result.HasValue)
{
- if (await staticFileHandler.HandleFileChangesAsync([fileItem], combinedCancellationSource.Token))
+ if (await staticFileHandler.HandleFileChangesAsync([fileSetTask.Result.Value], combinedCancellationSource.Token))
{
// We're able to handle the file change event without doing a full-rebuild.
continue;
@@ -131,7 +131,7 @@ public override async Task WatchAsync(CancellationToken cancellationToken)
Debug.Assert(finishedTask == fileSetTask);
changedFile = fileSetTask.Result;
Debug.Assert(changedFile != null, "ChangedFile should only be null when cancelled");
- Context.Reporter.Output($"File changed: {changedFile.Value.FilePath}");
+ Context.Reporter.Output($"File changed: {changedFile.Value.Item.FilePath}");
}
}
}
diff --git a/src/BuiltInTools/dotnet-watch/FileItem.cs b/src/BuiltInTools/dotnet-watch/FileItem.cs
index 71779898168a..7fc91cd6fa1c 100644
--- a/src/BuiltInTools/dotnet-watch/FileItem.cs
+++ b/src/BuiltInTools/dotnet-watch/FileItem.cs
@@ -1,9 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using Microsoft.DotNet.Watcher.Internal;
+
namespace Microsoft.DotNet.Watcher
{
- internal readonly struct FileItem
+ internal readonly record struct FileItem
{
public string FilePath { get; init; }
@@ -14,7 +16,7 @@ internal readonly struct FileItem
public string? StaticWebAssetPath { get; init; }
- public bool IsNewFile { get; init; }
+ public ChangeKind Change { get; init; }
public bool IsStaticFile => StaticWebAssetPath != null;
}
diff --git a/src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs b/src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs
index ad7599378ee2..0025fbbbcbf5 100644
--- a/src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs
+++ b/src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs
@@ -46,7 +46,7 @@ public IReadOnlyList GetProcessArguments(int iteration)
return [context.RootProjectOptions.Command, .. context.RootProjectOptions.CommandArguments];
}
- public async ValueTask EvaluateAsync(FileItem? changedFile, CancellationToken cancellationToken)
+ public async ValueTask EvaluateAsync(ChangedFile? changedFile, CancellationToken cancellationToken)
{
if (context.EnvironmentOptions.SuppressMSBuildIncrementalism)
{
@@ -54,7 +54,7 @@ public async ValueTask EvaluateAsync(FileItem? changedFile, Ca
return _evaluationResult = await CreateEvaluationResult(cancellationToken);
}
- if (_evaluationResult == null || RequiresMSBuildRevaluation(changedFile))
+ if (_evaluationResult == null || RequiresMSBuildRevaluation(changedFile?.Item))
{
RequiresRevaluation = true;
}
diff --git a/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs b/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs
index 17e4c6d27cd8..90ec68694946 100644
--- a/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs
+++ b/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs
@@ -1,20 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
-using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.ExternalAccess.Watch.Api;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.MSBuild;
-using Microsoft.Extensions.Tools.Internal;
-using Microsoft.CodeAnalysis.ExternalAccess.Watch.Api;
-using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Text;
+using Microsoft.DotNet.Watcher.Internal;
+using Microsoft.Extensions.Tools.Internal;
namespace Microsoft.DotNet.Watcher.Tools;
@@ -27,8 +22,11 @@ public IncrementalMSBuildWorkspace(IReporter reporter)
{
WorkspaceFailed += (_sender, diag) =>
{
- // Errors reported here are not fatal, an exception would be thrown for fatal issues.
- reporter.Verbose($"MSBuildWorkspace warning: {diag.Diagnostic}");
+ // Report both Warning and Failure as warnings.
+ // MSBuildProjectLoader reports Failures for cases where we can safely continue loading projects
+ // (e.g. non-C#/VB project is ignored).
+ // https://github.com/dotnet/roslyn/issues/75170
+ reporter.Warn($"msbuild: {diag.Diagnostic}", "⚠");
};
_reporter = reporter;
@@ -106,13 +104,24 @@ ImmutableArray MapDocuments(ProjectId mappedProjectId, IReadOnlyLi
}).ToImmutableArray();
}
- public async ValueTask UpdateFileContentAsync(IEnumerable changedFiles, CancellationToken cancellationToken)
+ public async ValueTask UpdateFileContentAsync(IEnumerable changedFiles, CancellationToken cancellationToken)
{
var updatedSolution = CurrentSolution;
- foreach (var changedFile in changedFiles)
+ var documentsToRemove = new List();
+
+ foreach (var (changedFile, change) in changedFiles)
{
+ // when a file is added we reevaluate the project:
+ Debug.Assert(change != ChangeKind.Add);
+
var documentIds = updatedSolution.GetDocumentIdsWithFilePath(changedFile.FilePath);
+ if (change == ChangeKind.Delete)
+ {
+ documentsToRemove.AddRange(documentIds);
+ continue;
+ }
+
foreach (var documentId in documentIds)
{
var textDocument = updatedSolution.GetDocument(documentId)
@@ -140,9 +149,17 @@ public async ValueTask UpdateFileContentAsync(IEnumerable changedFiles
}
}
+ updatedSolution = RemoveDocuments(updatedSolution, documentsToRemove);
+
await ReportSolutionFilesAsync(SetCurrentSolution(updatedSolution), cancellationToken);
}
+ private static Solution RemoveDocuments(Solution solution, IEnumerable ids)
+ => solution
+ .RemoveDocuments(ids.Where(id => solution.GetDocument(id) != null).ToImmutableArray())
+ .RemoveAdditionalDocuments(ids.Where(id => solution.GetAdditionalDocument(id) != null).ToImmutableArray())
+ .RemoveAnalyzerConfigDocuments(ids.Where(id => solution.GetAnalyzerConfigDocument(id) != null).ToImmutableArray());
+
private static async ValueTask GetSourceTextAsync(string filePath, CancellationToken cancellationToken)
{
var zeroLengthRetryPerformed = false;
diff --git a/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs b/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs
index dfe625075227..3a6961854987 100644
--- a/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs
+++ b/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs
@@ -5,6 +5,7 @@
using System.Collections;
using System.Diagnostics;
using Microsoft.Build.Graph;
+using Microsoft.DotNet.Watcher.Internal;
using Microsoft.Extensions.Tools.Internal;
namespace Microsoft.DotNet.Watcher.Tools
@@ -13,14 +14,14 @@ internal sealed class ScopedCssFileHandler(IReporter reporter, ProjectNodeMap pr
{
private const string BuildTargetName = "GenerateComputedBuildStaticWebAssets";
- public async ValueTask HandleFileChangesAsync(IReadOnlyList files, CancellationToken cancellationToken)
+ public async ValueTask HandleFileChangesAsync(IReadOnlyList files, CancellationToken cancellationToken)
{
var projectsToRefresh = new HashSet();
var hasApplicableFiles = false;
for (int i = 0; i < files.Count; i++)
{
- var file = files[i];
+ var file = files[i].Item;
if (!file.FilePath.EndsWith(".razor.css", StringComparison.Ordinal) &&
!file.FilePath.EndsWith(".cshtml.css", StringComparison.Ordinal))
diff --git a/src/BuiltInTools/dotnet-watch/HotReload/StaticFileHandler.cs b/src/BuiltInTools/dotnet-watch/HotReload/StaticFileHandler.cs
index f8d9b8bffd12..ce53bc8a7ce3 100644
--- a/src/BuiltInTools/dotnet-watch/HotReload/StaticFileHandler.cs
+++ b/src/BuiltInTools/dotnet-watch/HotReload/StaticFileHandler.cs
@@ -7,6 +7,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.CodeAnalysis.StackTraceExplorer;
+using Microsoft.DotNet.Watcher.Internal;
using Microsoft.Extensions.Tools.Internal;
namespace Microsoft.DotNet.Watcher.Tools
@@ -18,13 +19,13 @@ internal sealed class StaticFileHandler(IReporter reporter, ProjectNodeMap proje
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
- public async ValueTask HandleFileChangesAsync(IReadOnlyList files, CancellationToken cancellationToken)
+ public async ValueTask HandleFileChangesAsync(IReadOnlyList files, CancellationToken cancellationToken)
{
var allFilesHandled = true;
var refreshRequests = new Dictionary>();
for (int i = 0; i < files.Count; i++)
{
- var file = files[i];
+ var file = files[i].Item;
if (file.StaticWebAssetPath is null)
{
diff --git a/src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs b/src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs
index ee09b3c257df..24b543ebd9b3 100644
--- a/src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs
+++ b/src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs
@@ -74,7 +74,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke
HotReloadFileSetWatcher? fileSetWatcher = null;
EvaluationResult? evaluationResult = null;
RunningProject? rootRunningProject = null;
- Task? fileSetWatcherTask = null;
+ Task? fileSetWatcherTask = null;
try
{
@@ -178,7 +178,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke
// When a new file is added we need to run design-time build to find out
// what kind of the file it is and which project(s) does it belong to (can be linked, web asset, etc.).
// We don't need to rebuild and restart the application though.
- if (changedFiles.Any(f => f.IsNewFile))
+ if (changedFiles.Any(f => f.Change is ChangeKind.Add))
{
Context.Reporter.Verbose("File addition triggered re-evaluation.");
@@ -195,9 +195,9 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke
// update files in the change set with new evaluation info:
for (int i = 0; i < changedFiles.Length; i++)
{
- if (evaluationResult.Files.TryGetValue(changedFiles[i].FilePath, out var evaluatedFile))
+ if (evaluationResult.Files.TryGetValue(changedFiles[i].Item.FilePath, out var evaluatedFile))
{
- changedFiles[i] = evaluatedFile;
+ changedFiles[i] = changedFiles[i] with { Item = evaluatedFile };
}
}
@@ -336,24 +336,43 @@ await Task.WhenAll(
}
}
- private void ReportFileChanges(IReadOnlyList fileItems)
+ private void ReportFileChanges(IReadOnlyList changedFiles)
{
- Report(added: true);
- Report(added: false);
+ Report(kind: ChangeKind.Add);
+ Report(kind: ChangeKind.Update);
+ Report(kind: ChangeKind.Delete);
- void Report(bool added)
+ void Report(ChangeKind kind)
{
- var items = fileItems.Where(item => item.IsNewFile == added).ToArray();
+ var items = changedFiles.Where(item => item.Change == kind).ToArray();
if (items is not [])
{
- Context.Reporter.Output(GetMessage(items, added));
+ Context.Reporter.Output(GetMessage(items, kind));
}
}
- string GetMessage(IReadOnlyList items, bool added)
- => items is [var item]
- ? (added ? "File added: " : "File changed: ") + GetRelativeFilePath(item.FilePath)
- : (added ? "Files added: " : "Files changed: ") + string.Join(", ", items.Select(f => GetRelativeFilePath(f.FilePath)));
+ string GetMessage(IReadOnlyList items, ChangeKind kind)
+ => items is [{Item: var item }]
+ ? GetSingularMessage(kind) + ": " + GetRelativeFilePath(item.FilePath)
+ : GetPluralMessage(kind) + ": " + string.Join(", ", items.Select(f => GetRelativeFilePath(f.Item.FilePath)));
+
+ static string GetSingularMessage(ChangeKind kind)
+ => kind switch
+ {
+ ChangeKind.Update => "File updated",
+ ChangeKind.Add => "File added",
+ ChangeKind.Delete => "File deleted",
+ _ => throw new InvalidOperationException()
+ };
+
+ static string GetPluralMessage(ChangeKind kind)
+ => kind switch
+ {
+ ChangeKind.Update => "Files updated",
+ ChangeKind.Add => "Files added",
+ ChangeKind.Delete => "Files deleted",
+ _ => throw new InvalidOperationException()
+ };
}
private async ValueTask EvaluateRootProjectAsync(CancellationToken cancellationToken)
diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher.cs b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher.cs
index b844f8da4fa8..3a339bf63fb1 100644
--- a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher.cs
+++ b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher.cs
@@ -10,7 +10,7 @@ internal sealed class FileWatcher(IReadOnlyDictionary fileSet,
private readonly Dictionary _watchers = [];
private bool _disposed;
- public event Action? OnFileChange;
+ public event Action? OnFileChange;
public void Dispose()
{
@@ -73,9 +73,9 @@ private void WatcherErrorHandler(object? sender, Exception error)
}
}
- private void WatcherChangedHandler(object? sender, (string changedPath, bool newFile) args)
+ private void WatcherChangedHandler(object? sender, (string changedPath, ChangeKind kind) args)
{
- OnFileChange?.Invoke(args.changedPath, args.newFile);
+ OnFileChange?.Invoke(args.changedPath, args.kind);
}
private void DisposeWatcher(string directory)
@@ -101,22 +101,22 @@ private void EnsureNotDisposed()
private static string EnsureTrailingSlash(string path)
=> (path is [.., var last] && last != Path.DirectorySeparatorChar) ? path + Path.DirectorySeparatorChar : path;
- public async Task GetChangedFileAsync(Action? startedWatching, CancellationToken cancellationToken)
+ public async Task GetChangedFileAsync(Action? startedWatching, CancellationToken cancellationToken)
{
StartWatching();
- var fileChangedSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var fileChangedSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
cancellationToken.Register(() => fileChangedSource.TrySetResult(null));
- void FileChangedCallback(string path, bool newFile)
+ void FileChangedCallback(string path, ChangeKind kind)
{
if (fileSet.TryGetValue(path, out var fileItem))
{
- fileChangedSource.TrySetResult(fileItem);
+ fileChangedSource.TrySetResult(new ChangedFile(fileItem, kind));
}
}
- FileItem? changedFile;
+ ChangedFile? changedFile;
OnFileChange += FileChangedCallback;
try
diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/ChangeKind.cs b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/ChangeKind.cs
new file mode 100644
index 000000000000..5fef3b698624
--- /dev/null
+++ b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/ChangeKind.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.DotNet.Watcher.Internal;
+
+internal enum ChangeKind
+{
+ Update,
+ Add,
+ Delete
+}
+
+internal readonly record struct ChangedFile(FileItem Item, ChangeKind Change);
diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs
index ca622206e933..7040fe1a0763 100644
--- a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs
+++ b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs
@@ -33,7 +33,7 @@ internal DotnetFileWatcher(string watchedDirectory, Func? OnFileChange;
+ public event EventHandler<(string filePath, ChangeKind kind)>? OnFileChange;
public event EventHandler? OnError;
@@ -78,10 +78,10 @@ private void WatcherRenameHandler(object sender, RenamedEventArgs e)
return;
}
- Logger?.Invoke("Rename");
+ Logger?.Invoke($"Renamed '{e.OldFullPath}' to '{e.FullPath}'.");
- NotifyChange(e.OldFullPath, newFile: false);
- NotifyChange(e.FullPath, newFile: true);
+ NotifyChange(e.OldFullPath, ChangeKind.Delete);
+ NotifyChange(e.FullPath, ChangeKind.Add);
if (Directory.Exists(e.FullPath))
{
@@ -89,12 +89,23 @@ private void WatcherRenameHandler(object sender, RenamedEventArgs e)
{
// Calculated previous path of this moved item.
var oldLocation = Path.Combine(e.OldFullPath, newLocation.Substring(e.FullPath.Length + 1));
- NotifyChange(oldLocation, newFile: false);
- NotifyChange(newLocation, newFile: true);
+ NotifyChange(oldLocation, ChangeKind.Delete);
+ NotifyChange(newLocation, ChangeKind.Add);
}
}
}
+ private void WatcherDeletedHandler(object sender, FileSystemEventArgs e)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ Logger?.Invoke($"Deleted '{e.FullPath}'.");
+ NotifyChange(e.FullPath, ChangeKind.Delete);
+ }
+
private void WatcherChangeHandler(object sender, FileSystemEventArgs e)
{
if (_disposed)
@@ -102,8 +113,8 @@ private void WatcherChangeHandler(object sender, FileSystemEventArgs e)
return;
}
- Logger?.Invoke("Change");
- NotifyChange(e.FullPath, newFile: false);
+ Logger?.Invoke($"Updated '{e.FullPath}'.");
+ NotifyChange(e.FullPath, ChangeKind.Update);
}
private void WatcherAddedHandler(object sender, FileSystemEventArgs e)
@@ -113,14 +124,14 @@ private void WatcherAddedHandler(object sender, FileSystemEventArgs e)
return;
}
- Logger?.Invoke("Added");
- NotifyChange(e.FullPath, newFile: true);
+ Logger?.Invoke($"Added '{e.FullPath}'.");
+ NotifyChange(e.FullPath, ChangeKind.Add);
}
- private void NotifyChange(string fullPath, bool newFile)
+ private void NotifyChange(string fullPath, ChangeKind kind)
{
// Only report file changes
- OnFileChange?.Invoke(this, (fullPath, newFile));
+ OnFileChange?.Invoke(this, (fullPath, kind));
}
private void CreateFileSystemWatcher()
@@ -140,7 +151,7 @@ private void CreateFileSystemWatcher()
_fileSystemWatcher.IncludeSubdirectories = true;
_fileSystemWatcher.Created += WatcherAddedHandler;
- _fileSystemWatcher.Deleted += WatcherChangeHandler;
+ _fileSystemWatcher.Deleted += WatcherDeletedHandler;
_fileSystemWatcher.Changed += WatcherChangeHandler;
_fileSystemWatcher.Renamed += WatcherRenameHandler;
_fileSystemWatcher.Error += WatcherErrorHandler;
@@ -156,7 +167,7 @@ private void DisposeInnerWatcher()
_fileSystemWatcher.EnableRaisingEvents = false;
_fileSystemWatcher.Created -= WatcherAddedHandler;
- _fileSystemWatcher.Deleted -= WatcherChangeHandler;
+ _fileSystemWatcher.Deleted -= WatcherDeletedHandler;
_fileSystemWatcher.Changed -= WatcherChangeHandler;
_fileSystemWatcher.Renamed -= WatcherRenameHandler;
_fileSystemWatcher.Error -= WatcherErrorHandler;
diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/IFileSystemWatcher.cs b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/IFileSystemWatcher.cs
index f75c7e3e1481..ebdef49913ff 100644
--- a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/IFileSystemWatcher.cs
+++ b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/IFileSystemWatcher.cs
@@ -5,7 +5,7 @@ namespace Microsoft.DotNet.Watcher.Internal
{
internal interface IFileSystemWatcher : IDisposable
{
- event EventHandler<(string filePath, bool newFile)> OnFileChange;
+ event EventHandler<(string filePath, ChangeKind kind)> OnFileChange;
event EventHandler OnError;
diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/PollingFileWatcher.cs b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/PollingFileWatcher.cs
index da7630b71eb8..2df2004ee84b 100644
--- a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/PollingFileWatcher.cs
+++ b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/PollingFileWatcher.cs
@@ -15,7 +15,7 @@ internal class PollingFileWatcher : IFileSystemWatcher
private Dictionary _knownEntities = new();
private Dictionary _tempDictionary = new();
- private Dictionary _changes = new();
+ private Dictionary _changes = new();
private Thread _pollingThread;
private bool _raiseEvents;
@@ -40,7 +40,7 @@ public PollingFileWatcher(string watchedDirectory)
_pollingThread.Start();
}
- public event EventHandler<(string filePath, bool newFile)>? OnFileChange;
+ public event EventHandler<(string filePath, ChangeKind kind)>? OnFileChange;
#pragma warning disable CS0067 // not used
public event EventHandler? OnError;
@@ -106,8 +106,8 @@ private void CheckForChangedFiles()
if (!_knownEntities.ContainsKey(fullFilePath))
{
- // New file
- RecordChange(f, isNewFile: true);
+ // New file or directory
+ RecordChange(f, ChangeKind.Add);
}
else
{
@@ -115,10 +115,11 @@ private void CheckForChangedFiles()
try
{
- if (fileMeta.FileInfo.LastWriteTime != f.LastWriteTime)
+ if (!fileMeta.FileInfo.Attributes.HasFlag(FileAttributes.Directory) &&
+ fileMeta.FileInfo.LastWriteTime != f.LastWriteTime)
{
// File changed
- RecordChange(f, isNewFile: false);
+ RecordChange(f, ChangeKind.Update);
}
_knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, foundAgain: true);
@@ -136,8 +137,8 @@ private void CheckForChangedFiles()
{
if (!file.Value.FoundAgain)
{
- // File deleted
- RecordChange(file.Value.FileInfo, isNewFile: false);
+ // File or directory deleted
+ RecordChange(file.Value.FileInfo, ChangeKind.Delete);
}
}
@@ -148,7 +149,7 @@ private void CheckForChangedFiles()
_tempDictionary.Clear();
}
- private void RecordChange(FileSystemInfo fileInfo, bool isNewFile)
+ private void RecordChange(FileSystemInfo fileInfo, ChangeKind kind)
{
if (_changes.ContainsKey(fileInfo.FullName) ||
fileInfo.FullName.Equals(_watchedDirectory.FullName, StringComparison.Ordinal))
@@ -156,18 +157,15 @@ private void RecordChange(FileSystemInfo fileInfo, bool isNewFile)
return;
}
- _changes.Add(fileInfo.FullName, isNewFile);
+ _changes.Add(fileInfo.FullName, kind);
- if (fileInfo.FullName != _watchedDirectory.FullName)
+ if (fileInfo is FileInfo { Directory: { } directory })
{
- if (fileInfo is FileInfo { Directory: { } directory })
- {
- RecordChange(directory, isNewFile: false);
- }
- else if (fileInfo is DirectoryInfo { Parent: { } parent })
- {
- RecordChange(parent, isNewFile: false);
- }
+ RecordChange(directory, ChangeKind.Update);
+ }
+ else if (fileInfo is DirectoryInfo { Parent: { } parent })
+ {
+ RecordChange(parent, ChangeKind.Update);
}
}
@@ -202,14 +200,14 @@ private static void ForeachEntityInDirectory(DirectoryInfo dirInfo, Action fileSet, DateTime buildCompletionTime, IReporter reporter) : IDisposable
{
private static readonly TimeSpan s_debounceInterval = TimeSpan.FromMilliseconds(50);
+ private static readonly DateTime s_fileNotExistFileTime = DateTime.FromFileTime(0);
private readonly FileWatcher _fileWatcher = new(fileSet, reporter);
private readonly object _changedFilesLock = new();
- private readonly ConcurrentDictionary _changedFiles = new(StringComparer.Ordinal);
+ private readonly ConcurrentDictionary _changedFiles = new(StringComparer.Ordinal);
- private TaskCompletionSource? _tcs;
+ private TaskCompletionSource? _tcs;
private bool _initialized;
private bool _disposed;
@@ -65,7 +67,7 @@ private void EnsureInitialized()
continue;
}
- FileItem[] changedFiles;
+ ChangedFile[] changedFiles;
lock (_changedFilesLock)
{
changedFiles = _changedFiles.Values.ToArray();
@@ -82,7 +84,7 @@ private void EnsureInitialized()
}, default, TaskCreationOptions.LongRunning, TaskScheduler.Default);
- void FileChangedCallback(string path, bool newFile)
+ void FileChangedCallback(string path, ChangeKind kind)
{
// only handle file changes:
if (Directory.Exists(path))
@@ -90,43 +92,66 @@ void FileChangedCallback(string path, bool newFile)
return;
}
- try
+ if (kind != ChangeKind.Delete)
{
- // TODO: Deleted files will be ignored https://github.com/dotnet/sdk/issues/42535
-
- // Do not report changes to files that happened during build:
- var creationTime = File.GetCreationTimeUtc(path);
- var writeTime = File.GetLastWriteTimeUtc(path);
- if (creationTime.Ticks < buildCompletionTime.Ticks && writeTime.Ticks < buildCompletionTime.Ticks)
+ try
+ {
+ // Do not report changes to files that happened during build:
+ var creationTime = File.GetCreationTimeUtc(path);
+ var writeTime = File.GetLastWriteTimeUtc(path);
+
+ if (creationTime == s_fileNotExistFileTime || writeTime == s_fileNotExistFileTime)
+ {
+ // file might have been deleted since we received the event
+ kind = ChangeKind.Delete;
+ }
+ else if (creationTime.Ticks < buildCompletionTime.Ticks && writeTime.Ticks < buildCompletionTime.Ticks)
+ {
+ reporter.Verbose(
+ $"Ignoring file change during build: {kind} '{path}' " +
+ $"(created {FormatTimestamp(creationTime)} and written {FormatTimestamp(writeTime)} before {FormatTimestamp(buildCompletionTime)}).");
+
+ return;
+ }
+ else if (writeTime > creationTime)
+ {
+ reporter.Verbose($"File change: {kind} '{path}' (written {FormatTimestamp(writeTime)} after {FormatTimestamp(buildCompletionTime)}).");
+ }
+ else
+ {
+ reporter.Verbose($"File change: {kind} '{path}' (created {FormatTimestamp(creationTime)} after {FormatTimestamp(buildCompletionTime)}).");
+ }
+ }
+ catch (Exception e)
{
- reporter.Verbose($"Ignoring file updated during build: '{path}' ({FormatTimestamp(creationTime)},{FormatTimestamp(writeTime)} < {FormatTimestamp(buildCompletionTime)}).");
+ reporter.Verbose($"Ignoring file '{path}' due to access error: {e.Message}.");
return;
}
}
- catch (Exception e)
+
+ if (kind == ChangeKind.Delete)
{
- reporter.Verbose($"Ignoring file '{path}' due to access error: {e.Message}.");
- return;
+ reporter.Verbose($"File '{path}' deleted after {FormatTimestamp(buildCompletionTime)}.");
}
- if (newFile)
+ if (kind == ChangeKind.Add)
{
lock (_changedFilesLock)
{
- _changedFiles.TryAdd(path, new FileItem { FilePath = path, IsNewFile = newFile });
+ _changedFiles.TryAdd(path, new ChangedFile(new FileItem { FilePath = path }, kind));
}
}
else if (fileSet.TryGetValue(path, out var fileItem))
{
lock (_changedFilesLock)
{
- _changedFiles.TryAdd(path, fileItem);
+ _changedFiles.TryAdd(path, new ChangedFile(fileItem, kind));
}
}
}
}
- public Task GetChangedFilesAsync(CancellationToken cancellationToken, bool forceWaitForNewUpdate = false)
+ public Task GetChangedFilesAsync(CancellationToken cancellationToken, bool forceWaitForNewUpdate = false)
{
EnsureInitialized();
@@ -142,6 +167,6 @@ void FileChangedCallback(string path, bool newFile)
}
internal static string FormatTimestamp(DateTime time)
- => time.ToString("yyyy-MM-dd HH:mm:ss.fffffff");
+ => time.ToString("HH:mm:ss.fffffff");
}
}
diff --git a/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs b/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs
index 360e530e3b49..2bb88a290745 100644
--- a/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs
+++ b/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs
@@ -38,8 +38,8 @@ private static string GetCachedDeviceId()
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- // Get device Id from Windows registry
- using (var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\DeveloperTools"))
+ // Get device Id from Windows registry matching the OS architecture
+ using (var key = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64).OpenSubKey(@"SOFTWARE\Microsoft\DeveloperTools"))
{
deviceId = key?.GetValue("deviceid") as string;
}
@@ -80,10 +80,16 @@ private static void CacheDeviceId(string deviceId)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- // Cache device Id in Windows registry
- using (var key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Microsoft\DeveloperTools"))
+ // Cache device Id in Windows registry matching the OS architecture
+ using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64))
{
- key.SetValue("deviceid", deviceId);
+ using(var key = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\DeveloperTools"))
+ {
+ if (key != null)
+ {
+ key.SetValue("deviceid", deviceId);
+ }
+ }
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommand.cs b/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommand.cs
index 1499142be4c0..42b9dd56417e 100644
--- a/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommand.cs
+++ b/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommand.cs
@@ -8,6 +8,7 @@
using Microsoft.DotNet.ToolManifest;
using Microsoft.DotNet.ToolPackage;
using Microsoft.DotNet.Tools.Tool.Common;
+using Microsoft.Extensions.EnvironmentAbstractions;
using CreateShellShimRepository = Microsoft.DotNet.Tools.Tool.Install.CreateShellShimRepository;
namespace Microsoft.DotNet.Tools.Tool.Update
@@ -43,16 +44,17 @@ public ToolUpdateCommand(
localToolsResolverCache,
reporter);
+ _global = result.GetValue(ToolInstallCommandParser.GlobalOption);
+ _toolPath = result.GetValue(ToolInstallCommandParser.ToolPathOption);
+ DirectoryPath? location = string.IsNullOrWhiteSpace(_toolPath) ? null : new DirectoryPath(_toolPath);
_toolUpdateGlobalOrToolPathCommand =
toolUpdateGlobalOrToolPathCommand
?? new ToolUpdateGlobalOrToolPathCommand(
result,
createToolPackageStoreDownloaderUninstaller,
createShellShimRepository,
- reporter);
-
- _global = result.GetValue(ToolInstallCommandParser.GlobalOption);
- _toolPath = result.GetValue(ToolInstallCommandParser.ToolPathOption);
+ reporter,
+ ToolPackageFactory.CreateToolPackageStoreQuery(location));
}
diff --git a/src/Cli/dotnet/commands/dotnet-workload/list/VisualStudioWorkloads.cs b/src/Cli/dotnet/commands/dotnet-workload/list/VisualStudioWorkloads.cs
index 17258847bd3b..43553bb769e9 100644
--- a/src/Cli/dotnet/commands/dotnet-workload/list/VisualStudioWorkloads.cs
+++ b/src/Cli/dotnet/commands/dotnet-workload/list/VisualStudioWorkloads.cs
@@ -4,7 +4,6 @@
using System.Runtime.Versioning;
using Microsoft.Deployment.DotNet.Releases;
using Microsoft.DotNet.Cli.Utils;
-using Microsoft.DotNet.ToolPackage;
using Microsoft.DotNet.Workloads.Workload.Install;
using Microsoft.DotNet.Workloads.Workload.List;
using Microsoft.NET.Sdk.WorkloadManifestReader;
@@ -33,6 +32,16 @@ internal static class VisualStudioWorkloads
"Microsoft.VisualStudio.Product.Enterprise",
};
+ ///
+ /// Default prefix to use for Visual Studio component and component group IDs.
+ ///
+ private static readonly string s_visualStudioComponentPrefix = "Microsoft.NET.Component";
+
+ ///
+ /// Well-known prefixes used by some workloads that can be replaced when generating component IDs.
+ ///
+ private static readonly string[] s_wellKnownWorkloadPrefixes = { "Microsoft.NET.", "Microsoft." };
+
///
/// The SWIX package ID wrapping the SDK installer in Visual Studio. The ID should contain
/// the SDK version as a suffix, e.g., "Microsoft.NetCore.Toolset.5.0.403".
@@ -40,12 +49,44 @@ internal static class VisualStudioWorkloads
private static readonly string s_visualStudioSdkPackageIdPrefix = "Microsoft.NetCore.Toolset.";
///
- /// Gets a set of workload components based on the available set of workloads for the current SDK.
+ /// Gets a dictionary of mapping possible Visual Studio component IDs to .NET workload IDs in the current SDK.
///
/// The workload resolver used to obtain available workloads.
- /// A collection of Visual Studio component IDs corresponding to workload IDs.
- internal static IEnumerable GetAvailableVisualStudioWorkloads(IWorkloadResolver workloadResolver) =>
- workloadResolver.GetAvailableWorkloads().Select(w => w.Id.ToString().Replace('-', '.'));
+ /// A dictionary of Visual Studio component IDs corresponding to workload IDs.
+ internal static Dictionary GetAvailableVisualStudioWorkloads(IWorkloadResolver workloadResolver)
+ {
+ Dictionary visualStudioComponentWorkloads = new(StringComparer.OrdinalIgnoreCase);
+
+ // Iterate through all the available workload IDs and generate potential Visual Studio
+ // component IDs that map back to the original workload ID. This ensures that we
+ // can do reverse lookups for special cases where a workload ID contains a prefix
+ // corresponding with the full VS component ID prefix. For example,
+ // Microsoft.NET.Component.runtime.android would be a valid component ID for both
+ // microsoft-net-runtime-android and runtime-android.
+ foreach (var workload in workloadResolver.GetAvailableWorkloads())
+ {
+ string workloadId = workload.Id.ToString();
+ // Old style VS components simply replaced '-' with '.' in the workload ID.
+ string componentId = workload.Id.ToString().Replace('-', '.');
+
+ visualStudioComponentWorkloads.Add(componentId, workloadId);
+
+ // Starting in .NET 9.0 and VS 17.12, workload components will follow the VS naming convention.
+ foreach (string wellKnownPrefix in s_wellKnownWorkloadPrefixes)
+ {
+ if (componentId.StartsWith(wellKnownPrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ componentId = componentId.Substring(wellKnownPrefix.Length);
+ break;
+ }
+ }
+
+ componentId = s_visualStudioComponentPrefix + "." + componentId;
+ visualStudioComponentWorkloads.Add(componentId, workloadId);
+ }
+
+ return visualStudioComponentWorkloads;
+ }
///
/// Finds all workloads installed by all Visual Studio instances given that the
@@ -59,7 +100,7 @@ internal static IEnumerable GetAvailableVisualStudioWorkloads(IWorkloadR
internal static void GetInstalledWorkloads(IWorkloadResolver workloadResolver,
InstalledWorkloadsCollection installedWorkloads, SdkFeatureBand? sdkFeatureBand = null)
{
- IEnumerable visualStudioWorkloadIds = GetAvailableVisualStudioWorkloads(workloadResolver);
+ Dictionary visualStudioWorkloadIds = GetAvailableVisualStudioWorkloads(workloadResolver);
HashSet installedWorkloadComponents = new();
// Visual Studio instances contain a large set of packages and we have to perform a linear
@@ -105,10 +146,9 @@ internal static void GetInstalledWorkloads(IWorkloadResolver workloadResolver,
continue;
}
- if (visualStudioWorkloadIds.Contains(packageId, StringComparer.OrdinalIgnoreCase))
+ if (visualStudioWorkloadIds.TryGetValue(packageId, out string workloadId))
{
- // Normalize back to an SDK style workload ID.
- installedWorkloadComponents.Add(packageId.Replace('.', '-'));
+ installedWorkloadComponents.Add(workloadId);
}
}
diff --git a/src/Installer/finalizer/CMakeLists.txt b/src/Installer/finalizer/CMakeLists.txt
index 63baa941a090..47163924e66e 100644
--- a/src/Installer/finalizer/CMakeLists.txt
+++ b/src/Installer/finalizer/CMakeLists.txt
@@ -42,12 +42,15 @@ add_compile_options($<$:/we4703>) # Potentially uninitia
add_compile_options($<$:/we4789>) # destination of memory copy is too small
add_compile_options($<$:/we4995>) # 'function': name was marked as #pragma deprecated
add_compile_options($<$:/we4996>) # 'function': was declared deprecated also 'std::'
+add_compile_options($<$:/guard:cf>) # Enable control flow guard
add_executable(Finalizer
finalizer.cpp
native.rc
)
+add_link_options(/guard:cf)
+
# These are normally part of a .vcxproj in Visual Studio, but appears to be missing when CMAKE generates a .vcxproj for arm64.
target_link_libraries(Finalizer shell32.lib)
target_link_libraries(Finalizer advapi32.lib)
diff --git a/src/SourceBuild/content/Directory.Build.props b/src/SourceBuild/content/Directory.Build.props
index 9b9e79c842f9..8ed26023229b 100644
--- a/src/SourceBuild/content/Directory.Build.props
+++ b/src/SourceBuild/content/Directory.Build.props
@@ -171,7 +171,8 @@
$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'prebuilt-report'))
$([MSBuild]::NormalizeDirectory('$(PackageReportDir)', 'prebuilt-packages'))
-
+ $([MSBuild]::NormalizeDirectory('$(PackageReportDir)', '$(MSBuildProjectName)'))
+
$([MSBuild]::NormalizeDirectory('$(SrcDir)', 'source-build-reference-packages', 'src'))
$([MSBuild]::NormalizeDirectory('$(PrereqsPackagesDir)', 'reference'))
Private.SourceBuilt.Artifacts
diff --git a/src/SourceBuild/content/repo-projects/Directory.Build.targets b/src/SourceBuild/content/repo-projects/Directory.Build.targets
index ccb25f46dcf8..e95aefe47f4c 100644
--- a/src/SourceBuild/content/repo-projects/Directory.Build.targets
+++ b/src/SourceBuild/content/repo-projects/Directory.Build.targets
@@ -511,6 +511,19 @@
Condition="'@(_InnerPackageCacheFiles)' != ''" />
+
+
+
+
+
+
+
+
+
diff --git a/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/LicenseScanTests/LicenseExclusions.txt b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/LicenseScanTests/LicenseExclusions.txt
index 5e81d8c2fbaf..b324bfeab7a7 100644
--- a/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/LicenseScanTests/LicenseExclusions.txt
+++ b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/LicenseScanTests/LicenseExclusions.txt
@@ -172,6 +172,7 @@ src/runtime/src/libraries/System.Text.Json/roadmap/images/higher-level-component
# False positive
src/sdk/THIRD-PARTY-NOTICES.TXT|unknown-license-reference
+src/sdk/src/SourceBuild/patches/fsharp/0001-Fix17713-Reverting-PR-17649-Make-the-interaction-bet.patch|unknown-license-reference
# Configuration, doesn't apply to source directly
src/sdk/src/VirtualMonoRepo/THIRD-PARTY-NOTICES.template.txt
diff --git a/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/SdkContentTests/MsftToSbSdkFiles.diff b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/SdkContentTests/MsftToSbSdkFiles.diff
index 8cd6f6c82125..75b77d3c30c3 100644
--- a/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/SdkContentTests/MsftToSbSdkFiles.diff
+++ b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/SdkContentTests/MsftToSbSdkFiles.diff
@@ -45,7 +45,7 @@ index ------------
./packs/Microsoft.NETCore.App.Ref/x.y.z/
./packs/Microsoft.NETCore.App.Ref/x.y.z/analyzers/
@@ ------------ @@
- ./sdk-manifests/
+ ./sdk-manifests/x.y.z/
./sdk-manifests/x.y.z/
./sdk-manifests/x.y.z/
-./sdk-manifests/x.y.z/
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.EolTargetFrameworks.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.EolTargetFrameworks.targets
index d9bd74aa368f..8608d9f77a33 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.EolTargetFrameworks.targets
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.EolTargetFrameworks.targets
@@ -21,7 +21,7 @@ Copyright (c) .NET Foundation. All rights reserved.
receive servicing updates and security fixes.
-->
- <_EolNetCoreTargetFrameworkVersions Include="1.0;1.1;2.0;2.1;2.2;3.0;3.1;5.0" />
+ <_EolNetCoreTargetFrameworkVersions Include="1.0;1.1;2.0;2.1;2.2;3.0;3.1;5.0;7.0" />
<_MinimumNonEolSupportedNetCoreTargetFramework>net6.0
diff --git a/test/HelixTasks/AssemblyScheduler.cs b/test/HelixTasks/AssemblyScheduler.cs
index 65c431bad35d..428d40dff3f4 100644
--- a/test/HelixTasks/AssemblyScheduler.cs
+++ b/test/HelixTasks/AssemblyScheduler.cs
@@ -267,13 +267,14 @@ private static List GetTypeInfoList(MetadataReader reader)
///
private static bool ShouldIncludeType(MetadataReader reader, TypeDefinition type, int testMethodCount)
{
- // xunit only handles public, non-abstract classes
+ // xunit only handles public, non-abstract, non-generic classes
var isPublic =
- TypeAttributes.Public == (type.Attributes & TypeAttributes.Public) ||
- TypeAttributes.NestedPublic == (type.Attributes & TypeAttributes.NestedPublic);
+ TypeAttributes.Public == (type.Attributes & TypeAttributes.VisibilityMask) ||
+ TypeAttributes.NestedPublic == (type.Attributes & TypeAttributes.VisibilityMask);
if (!isPublic ||
TypeAttributes.Abstract == (type.Attributes & TypeAttributes.Abstract) ||
- TypeAttributes.Class != (type.Attributes & TypeAttributes.Class))
+ type.GetGenericParameters().Count != 0 ||
+ TypeAttributes.Class != (type.Attributes & TypeAttributes.ClassSemanticsMask))
{
return false;
}
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToTargetEolFrameworks.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToTargetEolFrameworks.cs
index 9f196512336e..6e020c0df98f 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToTargetEolFrameworks.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToTargetEolFrameworks.cs
@@ -15,6 +15,7 @@ public GivenThatWeWantToTargetEolFrameworks(ITestOutputHelper log) : base(log)
[InlineData("netcoreapp3.0")]
[InlineData("netcoreapp3.1")]
[InlineData("net5.0")]
+ [InlineData("net7.0")]
public void It_warns_that_framework_is_out_of_support(string targetFrameworks)
{
var testProject = new TestProject()
diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs
index 85473ebe5cd4..52ead18ac2c2 100644
--- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs
+++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs
@@ -44,7 +44,7 @@ public void NativeAot_hw_runs_with_no_warnings_when_PublishAot_is_enabled(string
var publishCommand = new PublishCommand(Log, Path.Combine(testAsset.TestRoot, testProject.Name));
publishCommand
- .Execute($"/p:UseCurrentRuntimeIdentifier=true", "/p:SelfContained=true")
+ .Execute($"/p:UseCurrentRuntimeIdentifier=true", "/p:SelfContained=true", "/p:CheckEolTargetFramework=false")
.Should().Pass()
.And.NotHaveStdOutContaining("IL2026")
.And.NotHaveStdErrContaining("NETSDK1179")
@@ -88,7 +88,7 @@ public void NativeAot_hw_runs_with_no_warnings_when_PublishAot_is_false(string t
var publishCommand = new PublishCommand(testAsset);
publishCommand
- .Execute($"/p:RuntimeIdentifier={rid}", "/p:SelfContained=true")
+ .Execute($"/p:RuntimeIdentifier={rid}", "/p:SelfContained=true", "/p:CheckEolTargetFramework=false")
.Should().Pass()
.And.NotHaveStdOutContaining("IL2026")
.And.NotHaveStdErrContaining("NETSDK1179")
diff --git a/test/Microsoft.WebTools.AspireService.Tests/AspireServerServiceTests.cs b/test/Microsoft.WebTools.AspireService.Tests/AspireServerServiceTests.cs
new file mode 100644
index 000000000000..69d06b8c0479
--- /dev/null
+++ b/test/Microsoft.WebTools.AspireService.Tests/AspireServerServiceTests.cs
@@ -0,0 +1,532 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable disable
+
+using System.Net;
+using System.Net.Http.Headers;
+using System.Net.WebSockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Text.Json;
+using Microsoft.WebTools.AspireServer.Helpers;
+using Microsoft.WebTools.AspireServer.Models;
+
+namespace Microsoft.WebTools.AspireServer.UnitTests;
+
+public class AspireServerServiceTests
+{
+ private const string Project1Path = @"c:\test\Projects\project1.csproj";
+ private const int ProcessId = 34213;
+ private const string DcpId = "myid";
+ private const string SpecificProfileName = "SpecificProfile";
+ private const string VersionedSessionUrl = $"{RunSessionRequest.Url}?{RunSessionRequest.VersionQuery}={RunSessionRequest.OurProtocolVersion}";
+
+ private static readonly TestRunSessionRequest Project1SessionRequest = new TestRunSessionRequest(Project1Path, debugging: false, launchProfile: null, disableLaunchProfile: false)
+ {
+ args = new List { "--project1Arg" },
+ env = new List { new EnvVar { Name = "var1", Value = "value1" } }
+ };
+
+ [Fact]
+ public async Task SessionStarted_Test()
+ {
+ var mocks = new Mocks();
+
+ var server = await GetAspireServer(mocks);
+
+ // Start listening
+ TaskCompletionSource connected = new();
+
+ TaskCompletionSource notificationTask = new();
+ _ = listenForSessionUpdatesAsync(server, connected, (sn) =>
+ {
+ notificationTask.SetResult((SessionChangeNotification)sn);
+ });
+
+ await connected.Task;
+
+ await server.SessionStartedAsync(DcpId,"1", ProcessId, CancellationToken.None);
+
+ var result = await notificationTask.Task;
+
+ Assert.Equal(ProcessId, result.PID);
+ Assert.Equal("1", result.SessionId);
+
+ await server.DisposeAsync();
+
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task SessionEndedAsync_Test()
+ {
+ var mocks = new Mocks();
+
+ var server = await GetAspireServer(mocks);
+
+ // Start listening
+ TaskCompletionSource connected = new();
+ TaskCompletionSource sessionEndNotificationTask = new();
+ _ = listenForSessionUpdatesAsync(server, connected, (sn) =>
+ {
+ if (sn.NotificationType == NotificationType.SessionTerminated)
+ {
+ sessionEndNotificationTask.SetResult((SessionChangeNotification)sn);
+ }
+ });
+
+ await connected.Task;
+
+ await server.SessionEndedAsync(DcpId, "1", ProcessId, 130, CancellationToken.None);
+
+ var result = await sessionEndNotificationTask.Task;
+ Assert.Equal(ProcessId, result.PID);
+ Assert.Equal("1", result.SessionId);
+ Assert.Equal(130, result.ExitCode);
+
+ await server.DisposeAsync();
+
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task LaunchProject_Success()
+ {
+ var mocks = new Mocks();
+
+ mocks.GetOrCreate()
+ .ImplementStartProjectAsync(DcpId, "2");
+
+ var server = await GetAspireServer(mocks);
+ var tokens = await server.GetServerVariablesAsync();
+
+ using HttpClient client = GetHttpClient(tokens);
+
+ HttpResponseMessage response;
+ response = await client.PutAsJsonAsync(VersionedSessionUrl, Project1SessionRequest);
+
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ Assert.Equal($"{client.BaseAddress}run_session/2", response.Headers.Location.AbsoluteUri);
+
+ await server.DisposeAsync();
+
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task LaunchProject_Success_ThenStopProcessRequest()
+ {
+ var mocks = new Mocks();
+
+ mocks.GetOrCreate()
+ .ImplementStartProjectAsync(DcpId, "2")
+ .ImplementStopSessionAsync(DcpId, "2", exists: true)
+ .ImplementStopSessionAsync(DcpId, "3", exists: false);
+
+ var server = await GetAspireServer(mocks);
+ var tokens = await server.GetServerVariablesAsync();
+
+ using HttpClient client = GetHttpClient(tokens);
+
+ var response = await client.PutAsJsonAsync(VersionedSessionUrl, Project1SessionRequest);
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+
+ // Now send a stop session
+ response = await client.DeleteAsync(RunSessionRequest.Url + "/2");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ // Validate NoContent response if session not found
+ response = await client.DeleteAsync(RunSessionRequest.Url + "/3");
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+
+ await server.DisposeAsync();
+
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task LaunchProject_FailedToLaunchProject()
+ {
+ var mocks = new Mocks();
+
+ mocks.GetOrCreate()
+ .ImplementStartProjectAsync(DcpId, "2", new Exception("Launch project failed"));
+
+ var server = await GetAspireServer(mocks);
+
+ var tokens = await server.GetServerVariablesAsync();
+ using HttpClient client = GetHttpClient(tokens);
+
+ var response = await client.PutAsJsonAsync(VersionedSessionUrl, Project1SessionRequest);
+
+ Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
+ Assert.Equal("{\"error\":{\"message\":\"Launch project failed\"}}", await response.Content.ReadAsStringAsync());
+
+ await server.DisposeAsync();
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task LaunchProject_FailNoBearerToken()
+ {
+ var mocks = new Mocks();
+
+ var server = await GetAspireServer(mocks);
+
+ var tokens = await server.GetServerVariablesAsync();
+ using HttpClient client = GetHttpClient(tokens);
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "badToken");
+
+ var response = await client.PutAsJsonAsync(VersionedSessionUrl, Project1SessionRequest);
+
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+
+ await server.DisposeAsync();
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task LaunchProject_FailWrongUrl()
+ {
+ var mocks = new Mocks();
+
+ var server = await GetAspireServer(mocks);
+
+ var tokens = await server.GetServerVariablesAsync();
+ using HttpClient client = GetHttpClient(tokens);
+
+ var response = await client.PutAsJsonAsync("/run_badurl", Project1SessionRequest);
+
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+
+ await server.DisposeAsync();
+
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task LaunchProject_NotAPUTRequest()
+ {
+ var mocks = new Mocks();
+
+ var aspireServer = await GetAspireServer(mocks);
+
+ var tokens = await aspireServer.GetServerVariablesAsync();
+ using HttpClient client = GetHttpClient(tokens);
+
+ var response = await client.PostAsJsonAsync(VersionedSessionUrl, Project1SessionRequest);
+
+ Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode);
+
+ await aspireServer.DisposeAsync();
+
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task StopSession_FailNoBearerToken()
+ {
+ var mocks = new Mocks();
+
+ var server = await GetAspireServer(mocks);
+
+ var tokens = await server.GetServerVariablesAsync();
+ using HttpClient client = GetHttpClient(tokens);
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "badToken");
+
+ var response = await client.DeleteAsync(RunSessionRequest.Url + "/2");
+
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+
+ await server.DisposeAsync();
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task Info_Success()
+ {
+ var mocks = new Mocks();
+
+ var server = await GetAspireServer(mocks);
+
+ var tokens = await server.GetServerVariablesAsync();
+ using HttpClient client = GetHttpClient(tokens);
+
+ var response = await client.GetAsync(InfoResponse.Url);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ await server.DisposeAsync();
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task Info_FailNoBearerToken()
+ {
+ var mocks = new Mocks();
+
+ var server = await GetAspireServer(mocks);
+
+ var tokens = await server.GetServerVariablesAsync();
+ using HttpClient client = GetHttpClient(tokens);
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "badToken");
+
+ var response = await client.GetAsync(InfoResponse.Url);
+
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+
+ await server.DisposeAsync();
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task SendLogMessageAsync_Test()
+ {
+ var mocks = new Mocks();
+
+ var aspireServer = await GetAspireServer(mocks);
+
+
+ // Start listening
+ TaskCompletionSource connected = new();
+ TaskCompletionSource notificationTask = new();
+ _ = listenForSessionUpdatesAsync(aspireServer, connected, (sn) =>
+ {
+ notificationTask.SetResult((SessionLogsNotification)sn);
+ });
+
+ await connected.Task;
+
+ await aspireServer.SendLogMessageAsync(DcpId, "1", isStdErr: false, "My Message", CancellationToken.None);
+
+ var result = await notificationTask.Task;
+
+ Assert.Equal("My Message", result.LogMessage);
+ Assert.False(result.IsStdErr);
+ await aspireServer.DisposeAsync();
+
+ mocks.Verify();
+ }
+
+ [Fact]
+ public async Task GetEnvironmentForOrchestrator_Tests()
+ {
+ var mocks = new Mocks();
+
+ var server = await GetAspireServer(mocks, waitForListening: false);
+
+ // First time should create a key
+ var envVars = await server.GetServerConnectionEnvironmentAsync(CancellationToken.None);
+
+ Assert.Equal(3, envVars.Count);
+ var token = envVars[1];
+ Assert.NotNull(token.Value);
+
+ // Should return the same
+ envVars = await server.GetServerConnectionEnvironmentAsync(CancellationToken.None);
+ Assert.Equal(token, envVars[1]);
+
+ mocks.Verify();
+ }
+
+ private async Task listenForSessionUpdatesAsync(AspireServerService aspireServer, TaskCompletionSource connected, Action callback)
+ {
+ var tokens = await aspireServer.GetServerVariablesAsync();
+ using var httpClient = GetHttpClient(tokens);
+
+ using var ws = new ClientWebSocket();
+ ws.Options.SetRequestHeader("Authorization", $"Bearer {tokens.bearerToken}");
+ try
+ {
+ await ws.ConnectAsync(new Uri($"wss://{tokens.serverAddress}{RunSessionRequest.Url}{SessionNotificationBase.Url}"), httpClient, CancellationToken.None);
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail("Could not connect to session update endpoint: " + ex.ToString());
+ connected.SetResult(false);
+ return;
+ }
+
+ connected.SetResult(true);
+
+ while (ws.State == WebSocketState.Open)
+ {
+ try
+ {
+ var (message, messageType) = await GetSocketMsgAsync(ws);
+
+ if (messageType == WebSocketMessageType.Close)
+ {
+ await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
+ return;
+ }
+ else
+ {
+ var notificationBase = JsonSerializer.Deserialize(message, AspireServerService.JsonSerializerOptions);
+ if (notificationBase is null)
+ {
+ Console.WriteLine("Unexpected null SessionNotificationBase message");
+ }
+ else if (notificationBase.NotificationType == NotificationType.ProcessRestarted || notificationBase.NotificationType == NotificationType.SessionTerminated)
+ {
+ var scn = JsonSerializer.Deserialize(message, AspireServerService.JsonSerializerOptions);
+ if (scn is null)
+ {
+ Assert.Fail("Unexpected null SessionChangeNotification message");
+ }
+ else
+ {
+ callback.Invoke(scn);
+ }
+ }
+ else if (notificationBase.NotificationType == NotificationType.ServiceLogs)
+ {
+ var sessionLogs = JsonSerializer.Deserialize(message, AspireServerService.JsonSerializerOptions);
+ if (sessionLogs is null)
+ {
+ Assert.Fail("Unexpected null SessionLogsNotification message");
+ }
+ else
+ {
+ callback.Invoke(sessionLogs);
+ }
+ }
+ }
+ }
+ catch
+ {
+ // This is expected if the connection is closed
+ return;
+ }
+ }
+ }
+
+ private static HttpClient GetHttpClient((string serverAddress, string bearerToken, string certToken) tokens)
+ {
+ HttpClient client;
+ var serverCert = X509CertificateLoader.LoadCertificate(Convert.FromBase64String(tokens.certToken));
+ var clientHandler = new HttpClientHandler()
+ {
+ ClientCertificateOptions = ClientCertificateOption.Manual,
+ SslProtocols = System.Security.Authentication.SslProtocols.None,
+ ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
+ {
+ return cert?.Thumbprint == serverCert.Thumbprint;
+ }
+ };
+
+ client = new HttpClient(clientHandler);
+ client.BaseAddress = new Uri($"https://{tokens.serverAddress}");
+
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.bearerToken);
+ client.DefaultRequestHeaders.Add(HttpContextExtensions.DCPInstanceIDHeader, DcpId);
+
+ return client;
+ }
+
+ private async Task<(string, WebSocketMessageType)> GetSocketMsgAsync(ClientWebSocket client)
+ {
+ var rcvBuffer = new ArraySegment(new byte[2048]);
+ WebSocketReceiveResult rcvResult = await client.ReceiveAsync(rcvBuffer, CancellationToken.None);
+ if (rcvResult.MessageType == WebSocketMessageType.Text)
+ {
+ byte[] msgBytes = rcvBuffer.Skip(rcvBuffer.Offset).Take(rcvResult.Count).ToArray();
+ return (Encoding.UTF8.GetString(msgBytes), rcvResult.MessageType);
+ }
+
+ return (null, rcvResult.MessageType);
+ }
+
+ private async Task GetAspireServer(Mocks mocks, bool waitForListening = true)
+ {
+ var ase = mocks.GetOrCreate();
+
+ var aspireServer = new AspireServerService(ase.Object, displayName: "Test server", Console.WriteLine);
+
+ if (waitForListening)
+ {
+ await aspireServer.WaitForListeningAsync();
+ }
+
+ return aspireServer;
+ }
+
+#pragma warning disable IDE1006 // Naming Styles
+ internal class TestRunSessionRequestP4
+ {
+ public string project_path { get; set; } = string.Empty;
+ public bool debug { get; set; }
+ public List env { get; set; } = new List();
+ public List args { get; set; } = new List();
+ public string launch_profile { get; set; }
+ public bool disable_launch_profile { get; set; }
+ }
+
+ internal class TestRunSessionRequest
+ {
+ public TestRunSessionRequest(string projectPath, bool debugging, string launchProfile, bool disableLaunchProfile)
+ {
+ launch_configurations = new TestLaunchConfiguration[]
+ {
+ new() {
+ project_path = projectPath,
+ type = RunSessionRequest.ProjectLaunchConfigurationType,
+ mode= debugging? RunSessionRequest.DebugLaunchMode : RunSessionRequest.NoDebugLaunchMode,
+ launch_profile = launchProfile,
+ disable_launch_profile = disableLaunchProfile
+ }
+ };
+ }
+ public TestLaunchConfiguration[] launch_configurations { get; set; }
+ public List env { get; set; } = new List();
+ public List args { get; set; } = new List();
+
+ public TestRunSessionRequestP4 ToTestRunSessionRequestP4()
+ {
+ var launchConfig = launch_configurations[0];
+ return new TestRunSessionRequestP4()
+ {
+ project_path = launchConfig.project_path,
+ debug = string.Equals(launchConfig.mode, RunSessionRequest.DebugLaunchMode, StringComparison.OrdinalIgnoreCase),
+ args = args,
+ env = env,
+ launch_profile = launchConfig.launch_profile,
+ disable_launch_profile = launchConfig.disable_launch_profile
+ };
+ }
+ }
+
+ internal class TestLaunchConfiguration
+ {
+ public string type { get; set; } = string.Empty;
+ public string project_path { get; set; } = string.Empty;
+ public string launch_profile { get; set; }
+ public bool disable_launch_profile { get; set; }
+ public string mode { get; set; } = string.Empty;
+ }
+
+ internal class TestStopSessionRequest
+ {
+ public string session_id { get; set; } = string.Empty;
+ }
+#pragma warning restore IDE1006 // Naming Styles
+}
+
+internal static class AspireServerServiceExtensions
+{
+ public static async Task WaitForListeningAsync(this AspireServerService aspireServer)
+ {
+ string serverAddress = (await aspireServer.GetServerVariablesAsync()).serverAddress;
+
+ // We need to wait on the port being available
+ await Helpers.CanConnectToPortAsync(new Uri($"http://{serverAddress}"), 5000, CancellationToken.None);
+
+ }
+
+ public static async Task<(string serverAddress, string bearerToken, string certToken)> GetServerVariablesAsync(this AspireServerService aspireServer)
+ {
+ var enVars = await aspireServer.GetServerConnectionEnvironmentAsync(CancellationToken.None);
+ return (enVars[0].Value, enVars[1].Value, enVars[2].Value);
+ }
+}
diff --git a/test/Microsoft.WebTools.AspireService.Tests/Microsoft.WebTools.AspireService.Tests.csproj b/test/Microsoft.WebTools.AspireService.Tests/Microsoft.WebTools.AspireService.Tests.csproj
new file mode 100644
index 000000000000..927652b1cd10
--- /dev/null
+++ b/test/Microsoft.WebTools.AspireService.Tests/Microsoft.WebTools.AspireService.Tests.csproj
@@ -0,0 +1,18 @@
+
+
+
+ $(SdkTargetFramework)
+ Microsoft.WebTools.AspireServer.UnitTests
+ enable
+ Exe
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.WebTools.AspireService.Tests/Mocks/Helpers.cs b/test/Microsoft.WebTools.AspireService.Tests/Mocks/Helpers.cs
new file mode 100644
index 000000000000..19f6d17f15c3
--- /dev/null
+++ b/test/Microsoft.WebTools.AspireService.Tests/Mocks/Helpers.cs
@@ -0,0 +1,181 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Microsoft.WebTools.AspireServer.UnitTests;
+
+public static class Helpers
+{
+ public static async Task CanConnectToPortAsync(Uri url, uint msToWait, CancellationToken cancelToken)
+ {
+ bool connected = false;
+ Socket? ipv4Socket = null;
+ Socket? ipv6Socket = null;
+
+ // Create a "client" socket on any available port
+ try
+ {
+ TimeoutSpan timeout = new(msToWait);
+ if (Socket.OSSupportsIPv4)
+ {
+ try
+ {
+ ipv4Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ ipv4Socket.Bind(new IPEndPoint(IPAddress.Any, 0));
+ }
+ catch (SocketException)
+ {
+ if (ipv4Socket != null)
+ {
+ ipv4Socket.Close();
+ ipv4Socket = null;
+ }
+ }
+ }
+ if (Socket.OSSupportsIPv6)
+ {
+ try
+ {
+ ipv6Socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
+ ipv6Socket.Bind(new IPEndPoint(IPAddress.IPv6Any, 0));
+ }
+ catch (SocketException)
+ {
+ if (ipv6Socket != null)
+ {
+ ipv6Socket.Close();
+ ipv6Socket = null;
+ }
+ }
+ }
+
+ // No sockets means we aren't connected
+ if (ipv6Socket == null && ipv4Socket == null)
+ {
+ return false;
+ }
+
+ // If we have an IP address we use that otherwise assume loopback
+ IPEndPoint ipv4ServerEndPoint;
+ IPEndPoint ipv6ServerEndPoint;
+ if (IPAddress.TryParse(url.Host, out var ipAddress))
+ {
+ ipv4ServerEndPoint = new IPEndPoint(ipAddress.AddressFamily == AddressFamily.InterNetwork ? ipAddress : IPAddress.Loopback, url.Port);
+ ipv6ServerEndPoint = new IPEndPoint(ipAddress.AddressFamily == AddressFamily.InterNetworkV6 ? ipAddress : IPAddress.IPv6Loopback, url.Port);
+ }
+ else
+ {
+ ipv4ServerEndPoint = new IPEndPoint(IPAddress.Loopback, url.Port);
+ ipv6ServerEndPoint = new IPEndPoint(IPAddress.IPv6Loopback, url.Port);
+ }
+
+ // If a process is passed in, we bail if it has exited
+ while (!connected && !timeout.Expired)
+ {
+ cancelToken.ThrowIfCancellationRequested();
+ if (ipv4Socket != null)
+ {
+ try
+ {
+ // Now use IOControl to set the calls non blocking
+ ipv4Socket.Blocking = false;
+ // Since we are non-blocking, the Connect should throw an error indicating
+ // it needs time to connect
+ ipv4Socket.Connect(ipv4ServerEndPoint);
+ }
+ catch (SocketException)
+ {
+ // Now ping retry and block for a millisecond timeout
+ ArrayList connectList = new ArrayList() {ipv4Socket};
+ Socket.Select(null, connectList, null, 1000 /*microSecond -- in here, 1 milli-second*/);
+ if (connectList.Count == 1)
+ {
+ connected = true;
+ break;
+ }
+ }
+ finally
+ {
+ // TODO: why do we set the sockets back to blocking?
+ ipv4Socket.Blocking = true;
+ }
+ }
+
+ // Now try IPV6
+ if (ipv6Socket != null)
+ {
+ // Couldn't connect with IPV4, so try IPV6
+ try
+ {
+ ipv6Socket.Blocking = false;
+ ipv6Socket.Connect(ipv6ServerEndPoint);
+ }
+ catch (SocketException)
+ {
+ // Ping retry
+ ArrayList connectList = new ArrayList() {ipv6Socket};
+ Socket.Select(null, connectList, null, 1000 /*microSecond -- in here, 1 milli-second*/);
+ if (connectList.Count == 1)
+ {
+ connected = true;
+ break;
+ }
+ }
+ finally
+ {
+ // TODO: why do we set the sockets back to blocking?
+ ipv6Socket.Blocking = true;
+ }
+ }
+
+ // Wait a bit and try again
+ await Task.Delay(20, cancelToken);
+ }
+
+ }
+ finally
+ {
+ if (ipv4Socket != null)
+ {
+ ipv4Socket.Close();
+ }
+
+ if (ipv6Socket != null)
+ {
+ ipv6Socket.Close();
+ }
+ }
+
+ return connected;
+ }
+}
+
+internal class TimeoutSpan
+{
+ private readonly long _duration;
+ private long _startingTickCount;
+
+ public TimeoutSpan(long durationInMilliseconds)
+ {
+ // There are 10000 ticks in a millisecond so need to adjust accordingly
+ _duration = durationInMilliseconds * 10000;
+ Reset();
+ }
+
+ public bool Expired
+ {
+ get
+ {
+ return _duration != 0 && (DateTime.UtcNow.Ticks - _startingTickCount) > _duration;
+ }
+ }
+
+ public void Reset()
+ {
+ // DateTime.UtcNow is way more efficient than DateTime.Now since it doesn't have to deal with locale, DST, etc
+ _startingTickCount = DateTime.UtcNow.Ticks;
+ }
+}
diff --git a/test/Microsoft.WebTools.AspireService.Tests/Mocks/IAspireServerEventsMock.cs b/test/Microsoft.WebTools.AspireService.Tests/Mocks/IAspireServerEventsMock.cs
new file mode 100644
index 000000000000..3c805871fdf4
--- /dev/null
+++ b/test/Microsoft.WebTools.AspireService.Tests/Mocks/IAspireServerEventsMock.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+
+using Microsoft.WebTools.AspireServer.Contracts;
+using Moq;
+
+namespace Microsoft.WebTools.AspireServer.UnitTests;
+
+internal class IAspireServerEventsMock : MockFactory
+{
+ public IAspireServerEventsMock(Mocks mocks, MockBehavior? mockBehavior = null)
+ : base(mocks, mockBehavior)
+ {
+ }
+
+ public IAspireServerEventsMock ImplementStartProjectAsync(string dcpId, string sessionId, Exception? ex = null)
+ {
+ MockObject.Setup(x => x.StartProjectAsync(dcpId, It.IsAny(), It.IsAny()))
+ .Returns(() =>
+ {
+ if (ex is not null)
+ {
+ throw ex;
+ }
+
+ return new ValueTask(sessionId);
+ })
+ .Verifiable();
+ return this;
+ }
+
+ public IAspireServerEventsMock ImplementStopSessionAsync(string dcpId, string sessionId, bool exists, Exception? ex = null)
+ {
+ MockObject.Setup(x => x.StopSessionAsync(dcpId, sessionId, It.IsAny()))
+ .Returns(() =>
+ {
+ if (ex is not null)
+ {
+ throw ex;
+ }
+
+ return new ValueTask(exists);
+ })
+ .Verifiable();
+ return this;
+ }
+}
diff --git a/test/Microsoft.WebTools.AspireService.Tests/Mocks/IServiceProviderMock.cs b/test/Microsoft.WebTools.AspireService.Tests/Mocks/IServiceProviderMock.cs
new file mode 100644
index 000000000000..c975028c392f
--- /dev/null
+++ b/test/Microsoft.WebTools.AspireService.Tests/Mocks/IServiceProviderMock.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Moq;
+
+namespace Microsoft.WebTools.AspireServer.UnitTests;
+
+internal class IServiceProviderMock : MockFactory
+{
+ public IServiceProviderMock(Mocks mocks, MockBehavior? mockBehavior = null)
+ : base(mocks, mockBehavior)
+ {
+ }
+
+ public IServiceProviderMock ImplementService(Type type, object service)
+ {
+ MockObject.Setup(x => x.GetService(type)).Returns(service);
+
+ return this;
+ }
+}
diff --git a/test/Microsoft.WebTools.AspireService.Tests/Mocks/MockFactory.cs b/test/Microsoft.WebTools.AspireService.Tests/Mocks/MockFactory.cs
new file mode 100644
index 000000000000..ef9f4467013c
--- /dev/null
+++ b/test/Microsoft.WebTools.AspireService.Tests/Mocks/MockFactory.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Moq;
+
+namespace Microsoft.WebTools.AspireServer.UnitTests;
+
+public interface IMockFactory
+{
+ void Verify();
+ object GetObject();
+}
+
+public class MockFactory : IMockFactory where T : class
+{
+ public MockFactory(Mocks mocks, MockBehavior? mockBehavior)
+ {
+ AllMocks = mocks;
+ MockObject = new Mock(mockBehavior ?? MockBehavior.Strict);
+ }
+
+ protected Mocks AllMocks { get; }
+ public Mock MockObject { get; }
+
+ public T Object => MockObject.Object;
+
+ public virtual void Verify()
+ {
+ MockObject.VerifyAll();
+ }
+
+ public object GetObject()
+ {
+ return Object;
+ }
+}
diff --git a/test/Microsoft.WebTools.AspireService.Tests/Mocks/Mocks.cs b/test/Microsoft.WebTools.AspireService.Tests/Mocks/Mocks.cs
new file mode 100644
index 000000000000..0b546ad5b20e
--- /dev/null
+++ b/test/Microsoft.WebTools.AspireService.Tests/Mocks/Mocks.cs
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using Moq;
+
+namespace Microsoft.WebTools.AspireServer.UnitTests;
+
+public class Mocks
+{
+ private readonly Dictionary _mockFactories = new();
+
+ public void Add(IMockFactory factory)
+ {
+ _mockFactories.Add(factory.GetType(), factory);
+ }
+
+ public T GetOrCreate(MockBehavior? mockBehavior = null) where T : IMockFactory
+ {
+ if (_mockFactories.TryGetValue(typeof(T), out var factory))
+ {
+ return (T)factory;
+ }
+
+ var newMock = (IMockFactory?)Activator.CreateInstance(typeof(T), this, mockBehavior);
+ Debug.Assert(newMock != null);
+ Add(newMock);
+ return (T)newMock;
+ }
+
+ public virtual void Verify()
+ {
+ foreach (var factory in _mockFactories)
+ {
+ factory.Value.Verify();
+ }
+ }
+}
diff --git a/test/Microsoft.WebTools.AspireService.Tests/Properties/AssemblyInfo.cs b/test/Microsoft.WebTools.AspireService.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000000..8b7c37cd90c4
--- /dev/null
+++ b/test/Microsoft.WebTools.AspireService.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
diff --git a/test/Microsoft.WebTools.AspireService.Tests/Properties/launchSettings.json b/test/Microsoft.WebTools.AspireService.Tests/Properties/launchSettings.json
new file mode 100644
index 000000000000..2d63805b76a3
--- /dev/null
+++ b/test/Microsoft.WebTools.AspireService.Tests/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "Microsoft.WebTools.AspireServer.Test": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:61653;http://localhost:61654"
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.WebTools.AspireService.Tests/RunSessionRequestTests.cs b/test/Microsoft.WebTools.AspireService.Tests/RunSessionRequestTests.cs
new file mode 100644
index 000000000000..4012d02bf10c
--- /dev/null
+++ b/test/Microsoft.WebTools.AspireService.Tests/RunSessionRequestTests.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable disable
+
+using Microsoft.WebTools.AspireServer.Models;
+
+namespace Microsoft.WebTools.AspireServer.UnitTests;
+
+public class RunSessionRequestTests
+{
+ [Fact]
+ public void RunSessionRequest_ToProjectLaunchRequest()
+ {
+ var runSessionReq = new RunSessionRequest()
+ {
+ Arguments = new string[] { "--someArg" },
+ Environment = new EnvVar[]
+ {
+ new EnvVar { Name = "var1", Value = "value1"},
+ new EnvVar { Name = "var2", Value = "value2"},
+ },
+ LaunchConfigurations = new LaunchConfiguration[]
+ {
+ new() {
+ ProjectPath = @"c:\test\Projects\project1.csproj",
+ LaunchType = RunSessionRequest.ProjectLaunchConfigurationType,
+ LaunchMode= RunSessionRequest.DebugLaunchMode,
+ LaunchProfile = "specificProfileName",
+ DisableLaunchProfile = true
+ }
+ }
+ };
+
+ var projectReq = runSessionReq.ToProjectLaunchInformation();
+
+ Assert.Equal(runSessionReq.Arguments[0], projectReq.Arguments.First());
+ Assert.Equal(runSessionReq.Environment.Length, projectReq.Environment.Count());
+ Assert.Equal(runSessionReq.Environment[0].Name, projectReq.Environment.First().Key);
+ Assert.Equal(runSessionReq.Environment[0].Value, projectReq.Environment.First().Value);
+ Assert.Equal(runSessionReq.LaunchConfigurations[0].ProjectPath, projectReq.ProjectPath);
+ Assert.True(projectReq.Debug);
+ Assert.Equal(runSessionReq.LaunchConfigurations[0].LaunchProfile, projectReq.LaunchProfile);
+ Assert.Equal(runSessionReq.LaunchConfigurations[0].DisableLaunchProfile, projectReq.DisableLaunchProfile);
+ }
+}
diff --git a/test/dotnet-new.Tests/CommonTemplatesTests.cs b/test/dotnet-new.Tests/CommonTemplatesTests.cs
index 6f598250a7ef..98c86dde4016 100644
--- a/test/dotnet-new.Tests/CommonTemplatesTests.cs
+++ b/test/dotnet-new.Tests/CommonTemplatesTests.cs
@@ -230,8 +230,8 @@ public async Task AotVariants(string name, string language)
var templatesToTest = new[]
{
- new { Template = consoleTemplateShortname, Frameworks = new[] { null, "net6.0", "net7.0", "net8.0" } },
- new { Template = "classlib", Frameworks = new[] { null, "net6.0", "net7.0", "net8.0", "netstandard2.0", "netstandard2.1" } }
+ new { Template = consoleTemplateShortname, Frameworks = new[] { null, "net6.0", "net8.0" } },
+ new { Template = "classlib", Frameworks = new[] { null, "net6.0", "net8.0", "netstandard2.0", "netstandard2.1" } }
};
//features: top-level statements; nullables; implicit usings; filescoped namespaces
@@ -240,9 +240,9 @@ public async Task AotVariants(string name, string language)
//C# 12 is not supported yet - https://github.com/dotnet/sdk/issues/29195
string?[] supportedLanguageVersions = { null, "ISO-2", "2", "3", "4", "5", "6", "7", "7.1", "7.2", "7.3", "8.0", "9.0", "10.0", "11.0", "11", /*"12",*/ "latest", "latestMajor", "default", "preview" };
- string?[] nullableSupportedInFrameworkByDefault = { null, "net6.0", "net7.0", "net8.0", "netstandard2.1" };
- string?[] implicitUsingsSupportedInFramework = { null, "net6.0", "net7.0", "net8.0" };
- string?[] fileScopedNamespacesSupportedFrameworkByDefault = { null, "net6.0", "net7.0", "net8.0" };
+ string?[] nullableSupportedInFrameworkByDefault = { null, "net6.0", "net8.0", "netstandard2.1" };
+ string?[] implicitUsingsSupportedInFramework = { null, "net6.0", "net8.0" };
+ string?[] fileScopedNamespacesSupportedFrameworkByDefault = { null, "net6.0", "net8.0" };
string?[] nullableSupportedLanguages = { "8.0", "9.0", "10.0", "11.0", "11", /*"12",*/ "latest", "latestMajor", "default", "preview" };
string?[] topLevelStatementSupportedLanguages = { null, "9.0", "10.0", "11", "11.0", /*"12",*/ "latest", "latestMajor", "default", "preview" };
diff --git a/test/dotnet-watch.Tests/FileWatcherTests.cs b/test/dotnet-watch.Tests/FileWatcherTests.cs
index 03afa158fb79..eeadd08ceefd 100644
--- a/test/dotnet-watch.Tests/FileWatcherTests.cs
+++ b/test/dotnet-watch.Tests/FileWatcherTests.cs
@@ -1,53 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Testing;
using Microsoft.DotNet.Watcher.Internal;
namespace Microsoft.DotNet.Watcher.Tools
{
- public class FileWatcherTests
+ public class FileWatcherTests(ITestOutputHelper output)
{
- public FileWatcherTests(ITestOutputHelper output)
- {
- _output = output;
- _testAssetManager = new TestAssetsManager(output);
- }
-
private readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60);
private readonly TimeSpan NegativeTimeout = TimeSpan.FromSeconds(5);
- private readonly ITestOutputHelper _output;
- private readonly TestAssetsManager _testAssetManager;
+ private readonly TestAssetsManager _testAssetManager = new TestAssetsManager(output);
private async Task TestOperation(
string dir,
- (string, bool)[] expectedChanges,
+ (string path, ChangeKind kind)[] expectedChanges,
bool usePolling,
Action operation)
{
- // 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);
-
using var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling);
if (watcher is DotnetFileWatcher dotnetWatcher)
{
- dotnetWatcher.Logger = m => _output.WriteLine(m);
+ dotnetWatcher.Logger = m => output.WriteLine(m);
}
var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- var filesChanged = new HashSet<(string, bool)>();
+ var filesChanged = new HashSet<(string path, ChangeKind kind)>();
- var testFileFullPath = Path.Combine(dir, "foo");
-
- EventHandler<(string path, bool newFile)> handler = null;
+ EventHandler<(string path, ChangeKind kind)> handler = null;
handler = (_, f) =>
{
- filesChanged.Add(f);
+ if (filesChanged.Add(f))
+ {
+ output.WriteLine($"Observed new {f.kind}: '{f.path}' ({filesChanged.Count} out of {expectedChanges.Length})");
+ }
+ else
+ {
+ output.WriteLine($"Already seen {f.kind}: '{f.path}'");
+ }
if (filesChanged.Count == expectedChanges.Length)
{
@@ -88,17 +78,48 @@ await TestOperation(
expectedChanges: !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !usePolling
? new[]
{
- (testFileFullPath, false),
- (testFileFullPath, true),
+ (testFileFullPath, ChangeKind.Update),
+ (testFileFullPath, ChangeKind.Add),
}
: new[]
{
- (testFileFullPath, true),
+ (testFileFullPath, ChangeKind.Add),
},
usePolling,
() => File.WriteAllText(testFileFullPath, string.Empty));
}
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task NewFileInNewDirectory(bool usePolling)
+ {
+ var dir = _testAssetManager.CreateTestDirectory(identifier: usePolling.ToString()).Path;
+
+ var newDir = Path.Combine(dir, "Dir");
+ var newFile = Path.Combine(newDir, "foo");
+
+ await TestOperation(
+ dir,
+ expectedChanges: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !usePolling
+ ? new[]
+ {
+ (newDir, ChangeKind.Add),
+ (newFile, ChangeKind.Update),
+ (newFile, ChangeKind.Add),
+ }
+ : new[]
+ {
+ (newDir, ChangeKind.Add),
+ },
+ usePolling,
+ () =>
+ {
+ Directory.CreateDirectory(newDir);
+ File.WriteAllText(newFile, string.Empty);
+ });
+ }
+
[Theory]
[InlineData(true)]
[InlineData(false)]
@@ -111,14 +132,13 @@ public async Task ChangeFile(bool usePolling)
await TestOperation(
dir,
- expectedChanges: [(testFileFullPath, false)],
+ expectedChanges: [(testFileFullPath, ChangeKind.Update)],
usePolling,
() => File.WriteAllText(testFileFullPath, string.Empty));
}
[Theory]
- [InlineData(true)]
- [InlineData(false)]
+ [CombinatorialData]
public async Task MoveFile(bool usePolling)
{
var dir = _testAssetManager.CreateTestDirectory(identifier: usePolling.ToString()).Path;
@@ -129,18 +149,19 @@ public async Task MoveFile(bool usePolling)
await TestOperation(
dir,
- expectedChanges: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !usePolling
- ? new[]
- {
- (srcFile, false),
- (srcFile, true),
- (dstFile, true),
- }
- : new[]
- {
- (srcFile, false),
- (dstFile, true),
- },
+ expectedChanges: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !usePolling ?
+ [
+ // On OSX events from before we started observing are reported as well.
+ (srcFile, ChangeKind.Update),
+ (srcFile, ChangeKind.Add),
+ (srcFile, ChangeKind.Delete),
+ (dstFile, ChangeKind.Add),
+ ]
+ :
+ [
+ (srcFile, ChangeKind.Delete),
+ (dstFile, ChangeKind.Add),
+ ],
usePolling,
() => File.Move(srcFile, dstFile));
@@ -160,8 +181,8 @@ public async Task FileInSubdirectory()
await TestOperation(
dir,
expectedChanges: [
- (subdir, false),
- (testFileFullPath, false)
+ (subdir, ChangeKind.Update),
+ (testFileFullPath, ChangeKind.Update)
],
usePolling: true,
() => File.WriteAllText(testFileFullPath, string.Empty));
@@ -246,7 +267,7 @@ public async Task MultipleFiles(bool usePolling)
await TestOperation(
dir,
- expectedChanges: [(testFileFullPath, false)],
+ expectedChanges: [(testFileFullPath, ChangeKind.Update)],
usePolling: true,
() => File.WriteAllText(testFileFullPath, string.Empty));
}
@@ -274,9 +295,9 @@ private async Task AssertFileChangeRaisesEvent(string directory, IFileSystemWatc
{
var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var expectedPath = Path.Combine(directory, Path.GetRandomFileName());
- EventHandler<(string, bool)> handler = (_, f) =>
+ EventHandler<(string, ChangeKind)> handler = (_, f) =>
{
- _output.WriteLine("File changed: " + f);
+ output.WriteLine("File changed: " + f);
try
{
if (string.Equals(f.Item1, expectedPath, StringComparison.OrdinalIgnoreCase))
@@ -331,13 +352,43 @@ public async Task DeleteSubfolder(bool usePolling)
await TestOperation(
dir,
- expectedChanges: [
- (subdir, false),
- (f1, false),
- (f2, false),
- (f3, false),
+ expectedChanges: usePolling ?
+ [
+ (subdir, ChangeKind.Delete),
+ (f1, ChangeKind.Delete),
+ (f2, ChangeKind.Delete),
+ (f3, ChangeKind.Delete),
+ ]
+ : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
+ [
+ (subdir, ChangeKind.Add),
+ (subdir, ChangeKind.Delete),
+ (f1, ChangeKind.Update),
+ (f1, ChangeKind.Add),
+ (f1, ChangeKind.Delete),
+ (f2, ChangeKind.Update),
+ (f2, ChangeKind.Add),
+ (f2, ChangeKind.Delete),
+ (f3, ChangeKind.Update),
+ (f3, ChangeKind.Add),
+ (f3, ChangeKind.Delete),
+ ]
+ : RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
+ [
+ (subdir, ChangeKind.Update),
+ (subdir, ChangeKind.Delete),
+ (f1, ChangeKind.Delete),
+ (f2, ChangeKind.Delete),
+ (f3, ChangeKind.Delete),
+ ]
+ :
+ [
+ (subdir, ChangeKind.Delete),
+ (f1, ChangeKind.Delete),
+ (f2, ChangeKind.Delete),
+ (f3, ChangeKind.Delete),
],
- usePolling: true,
+ usePolling,
() => Directory.Delete(subdir, recursive: true));
}
}
diff --git a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs
index b98819b8993c..e3e3fef94f89 100644
--- a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs
+++ b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs
@@ -121,6 +121,27 @@ public async Task BlazorWasm()
//await App.AssertOutputLineStartsWith(MessageDescriptor.HotReloadSucceeded);
}
+ [Fact]
+ public async Task BlazorWasm_MSBuildWarning()
+ {
+ var testAsset = TestAssets
+ .CopyTestAsset("WatchBlazorWasm")
+ .WithSource()
+ .WithProjectChanges(proj =>
+ {
+ proj.Root.Descendants()
+ .Single(e => e.Name.LocalName == "ItemGroup")
+ .Add(XElement.Parse("""
+
+ """));
+ });
+
+ App.Start(testAsset, [], testFlags: TestFlags.MockBrowser);
+
+ await App.AssertOutputLineStartsWith("dotnet watch ⚠ msbuild: [Warning] Duplicate source file");
+ await App.AssertWaitingForChanges();
+ }
+
// Test is timing out on .NET Framework: https://github.com/dotnet/sdk/issues/41669
[CoreMSBuildOnlyFact]
public async Task HandleMissingAssemblyFailure()
@@ -157,5 +178,112 @@ public static void Print()
await App.AssertOutputLineStartsWith("Updated types: Printer");
}
+
+ [Theory]
+ [InlineData(true, Skip = "https://github.com/dotnet/sdk/issues/43320")]
+ [InlineData(false)]
+ public async Task RenameSourceFile(bool useMove)
+ {
+ Logger.WriteLine("RenameSourceFile started");
+
+ var testAsset = TestAssets.CopyTestAsset("WatchAppWithProjectDeps")
+ .WithSource();
+
+ var dependencyDir = Path.Combine(testAsset.Path, "Dependency");
+ var oldFilePath = Path.Combine(dependencyDir, "Foo.cs");
+ var newFilePath = Path.Combine(dependencyDir, "Renamed.cs");
+
+ var source = """
+ using System;
+ using System.IO;
+ using System.Runtime.CompilerServices;
+
+ public class Lib
+ {
+ public static void Print() => PrintFileName();
+
+ public static void PrintFileName([CallerFilePathAttribute] string filePath = null)
+ {
+ Console.WriteLine($"> {Path.GetFileName(filePath)}");
+ }
+ }
+ """;
+
+ File.WriteAllText(oldFilePath, source);
+
+ App.Start(testAsset, [], "AppWithDeps");
+
+ await App.AssertWaitingForChanges();
+
+ // rename the file:
+ if (useMove)
+ {
+ File.Move(oldFilePath, newFilePath);
+ }
+ else
+ {
+ File.Delete(oldFilePath);
+ File.WriteAllText(newFilePath, source);
+ }
+
+ Logger.WriteLine($"Renamed '{oldFilePath}' to '{newFilePath}'.");
+
+ await App.AssertOutputLineStartsWith("> Renamed.cs");
+ }
+
+ [Theory]
+ [InlineData(true, Skip = "https://github.com/dotnet/sdk/issues/43320")]
+ [InlineData(false)]
+ public async Task RenameDirectory(bool useMove)
+ {
+ Logger.WriteLine("RenameSourceFile started");
+
+ var testAsset = TestAssets.CopyTestAsset("WatchAppWithProjectDeps")
+ .WithSource();
+
+ var dependencyDir = Path.Combine(testAsset.Path, "Dependency");
+ var oldSubdir = Path.Combine(dependencyDir, "Subdir");
+ var newSubdir = Path.Combine(dependencyDir, "NewSubdir");
+
+ var source = """
+ using System;
+ using System.IO;
+ using System.Runtime.CompilerServices;
+
+ public class Lib
+ {
+ public static void Print() => PrintDirectoryName();
+
+ public static void PrintDirectoryName([CallerFilePathAttribute] string filePath = null)
+ {
+ Console.WriteLine($"> {Path.GetFileName(Path.GetDirectoryName(filePath))}");
+ }
+ }
+ """;
+
+ File.Delete(Path.Combine(dependencyDir, "Foo.cs"));
+ Directory.CreateDirectory(oldSubdir);
+ File.WriteAllText(Path.Combine(oldSubdir, "Foo.cs"), source);
+
+ App.Start(testAsset, [], "AppWithDeps");
+
+ await App.AssertWaitingForChanges();
+
+ // rename the directory:
+ if (useMove)
+ {
+ Directory.Move(oldSubdir, newSubdir);
+ }
+ else
+ {
+ Directory.Delete(oldSubdir, recursive: true);
+ Directory.CreateDirectory(newSubdir);
+ File.WriteAllText(Path.Combine(newSubdir, "Foo.cs"), source);
+ }
+
+ Logger.WriteLine($"Renamed '{oldSubdir}' to '{newSubdir}'.");
+
+ await App.AssertOutputLineStartsWith("> NewSubdir");
+ }
}
}
diff --git a/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs b/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs
index 0a64ca3f0c33..6b0aa27c851a 100644
--- a/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs
+++ b/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using Microsoft.DotNet.Watcher.Internal;
using Microsoft.Extensions.Tools.Internal;
namespace Microsoft.DotNet.Watcher.Tools
@@ -27,7 +28,7 @@ public async Task ProcessAsync_EvaluatesFileSetIfProjFileChanges()
evaluator.RequiresRevaluation = false;
- await evaluator.EvaluateAsync(changedFile: new() { FilePath = "Test.csproj" }, CancellationToken.None);
+ await evaluator.EvaluateAsync(changedFile: new(new() { FilePath = "Test.csproj" }, ChangeKind.Update), CancellationToken.None);
Assert.True(evaluator.RequiresRevaluation);
}
@@ -51,7 +52,7 @@ public async Task ProcessAsync_DoesNotEvaluateFileSetIfNonProjFileChanges()
evaluator.RequiresRevaluation = false;
- await evaluator.EvaluateAsync(changedFile: new() { FilePath = "Controller.cs" }, CancellationToken.None);
+ await evaluator.EvaluateAsync(changedFile: new(new() { FilePath = "Controller.cs" }, ChangeKind.Update), CancellationToken.None);
Assert.False(evaluator.RequiresRevaluation);
Assert.Equal(1, counter);
@@ -77,7 +78,7 @@ public async Task ProcessAsync_EvaluateFileSetOnEveryChangeIfOptimizationIsSuppr
evaluator.RequiresRevaluation = false;
- await evaluator.EvaluateAsync(changedFile: new() { FilePath = "Controller.cs" }, CancellationToken.None);
+ await evaluator.EvaluateAsync(changedFile: new(new() { FilePath = "Controller.cs" }, ChangeKind.Update), CancellationToken.None);
Assert.True(evaluator.RequiresRevaluation);
Assert.Equal(2, counter);
@@ -118,7 +119,7 @@ public async Task ProcessAsync_SetsEvaluationRequired_IfMSBuildFileChanges_ButIs
evaluator.RequiresRevaluation = false;
evaluator.Timestamps["Proj.csproj"] = new DateTime(1007);
- await evaluator.EvaluateAsync(new() { FilePath = "Controller.cs" }, CancellationToken.None);
+ await evaluator.EvaluateAsync(new(new() { FilePath = "Controller.cs" }, ChangeKind.Update), CancellationToken.None);
Assert.True(evaluator.RequiresRevaluation);
}
diff --git a/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs b/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs
index e716d587d4cf..ea89c17f57bd 100644
--- a/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs
+++ b/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs
@@ -51,12 +51,16 @@ public async Task AssertOutputLineStartsWith(string expectedPrefix, Pred
success: line => line.StartsWith(expectedPrefix, StringComparison.Ordinal),
failure: failure ?? new Predicate(line => line.Contains(WatchErrorOutputEmoji, StringComparison.Ordinal)));
- if (line == null && failure != null)
+ if (line == null)
{
- Assert.Fail($"Failed to find expected text: '{expectedPrefix}'");
+ Assert.Fail(failure != null
+ ? "Encountered failure condition"
+ : $"Failed to find expected prefix: '{expectedPrefix}'");
+ }
+ else
+ {
+ Assert.StartsWith(expectedPrefix, line, StringComparison.Ordinal);
}
-
- Assert.StartsWith(expectedPrefix, line, StringComparison.Ordinal);
return line.Substring(expectedPrefix.Length);
}
diff --git a/test/dotnet-workload-list.Tests/GivenDotnetWorkloadList.cs b/test/dotnet-workload-list.Tests/GivenDotnetWorkloadList.cs
index d933851bfdb9..80216c85cde3 100644
--- a/test/dotnet-workload-list.Tests/GivenDotnetWorkloadList.cs
+++ b/test/dotnet-workload-list.Tests/GivenDotnetWorkloadList.cs
@@ -3,6 +3,7 @@
using System.CommandLine;
using ManifestReaderTests;
+using Microsoft.DotNet.Workloads.Workload;
using Microsoft.DotNet.Workloads.Workload.List;
using Microsoft.NET.Sdk.WorkloadManifestReader;
using ListStrings = Microsoft.DotNet.Workloads.Workload.List.LocalizableStrings;
@@ -37,6 +38,18 @@ public void GivenNoWorkloadsAreInstalledListIsEmpty()
_reporter.Lines.Count.Should().Be(6);
}
+ [WindowsOnlyFact]
+ public void GivenAvailableWorkloadsItCanComputeVisualStudioIds()
+ {
+ var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(("SampleManifest", _manifestPath, "5.0.0", "6.0.100")), Directory.GetCurrentDirectory());
+
+#pragma warning disable CA1416 // Validate platform compatibility
+ var availableWorkloads = VisualStudioWorkloads.GetAvailableVisualStudioWorkloads(workloadResolver);
+ availableWorkloads.Should().Contain("mock.workload.1", "mock-workload-1");
+ availableWorkloads.Should().Contain("Microsoft.NET.Component.mock.workload.1", "mock-workload-1");
+#pragma warning restore CA1416 // Validate platform compatibility
+ }
+
[Fact]
public void GivenNoWorkloadsAreInstalledMachineReadableListIsEmpty()
{
diff --git a/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs b/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs
index 44b132f72971..575f0f6ebd0c 100644
--- a/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs
+++ b/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs
@@ -50,12 +50,16 @@ public void TelemetryCommonPropertiesShouldReturnNewGuidWhenCannotGetMacAddress(
}
[Fact]
- public void TelemetryCommonPropertiesShouldReturnNewGuidWhenCannotDevDeviceId()
+ public void TelemetryCommonPropertiesShouldEnsureDevDeviceIDIsCached()
{
var unitUnderTest = new TelemetryCommonProperties(userLevelCacheWriter: new NothingCache());
var assignedMachineId = unitUnderTest.GetTelemetryCommonProperties()["devdeviceid"];
Guid.TryParse(assignedMachineId, out var _).Should().BeTrue("it should be a guid");
+ var secondAssignedMachineId = unitUnderTest.GetTelemetryCommonProperties()["devdeviceid"];
+
+ Guid.TryParse(secondAssignedMachineId, out var _).Should().BeTrue("it should be a guid");
+ secondAssignedMachineId.Should().Be(assignedMachineId, "it should match the previously assigned guid");
}
[Fact]