diff --git a/FsAutoComplete.sln b/FsAutoComplete.sln index 880144ac3..8d8bd12ea 100644 --- a/FsAutoComplete.sln +++ b/FsAutoComplete.sln @@ -1,84 +1,155 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.452 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{1BE8AF57-B314-4C92-82A9-64CD9B7A4990}" - ProjectSection(SolutionItems) = preProject - paket.dependencies = paket.dependencies - paket.lock = paket.lock - EndProjectSection -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FsAutoComplete", "src\FsAutoComplete\FsAutoComplete.fsproj", "{B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FsAutoComplete.Core", "src\FsAutoComplete.Core\FsAutoComplete.Core.fsproj", "{4E4786F3-4566-44E1-8787-91790007F0F6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{443E0B8D-9AD0-436E-A331-E8CC12965F07}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FsAutoComplete.Tests.Lsp", "test\FsAutoComplete.Tests.Lsp\FsAutoComplete.Tests.Lsp.fsproj", "{6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BA56455D-4AEA-45FC-A569-027A68A76BA6}" -EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsAutoComplete.Logging", "src\FsAutoComplete.Logging\FsAutoComplete.Logging.fsproj", "{38C1F619-3E1E-4784-9833-E8A2AA95CDAE}" -EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "OptionAnalyzer", "test\OptionAnalyzer\OptionAnalyzer.fsproj", "{14C55B44-2063-4891-98BE-8184CAB1BE87}" -EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsAutoComplete.DependencyManager.Dummy", "test\FsAutoComplete.DependencyManager.Dummy\FsAutoComplete.DependencyManager.Dummy.fsproj", "{C58701B0-D8E3-4B68-A7DE-8524C95F86C0}" -EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "benchmarks", "benchmarks\benchmarks.fsproj", "{0CD029D8-B39E-4CBE-A190-C84A7A811180}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Release|Any CPU.Build.0 = Release|Any CPU - {4E4786F3-4566-44E1-8787-91790007F0F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4E4786F3-4566-44E1-8787-91790007F0F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E4786F3-4566-44E1-8787-91790007F0F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4E4786F3-4566-44E1-8787-91790007F0F6}.Release|Any CPU.Build.0 = Release|Any CPU - {775C1714-AD5F-4A4C-B613-0AE08F51E17A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {775C1714-AD5F-4A4C-B613-0AE08F51E17A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {775C1714-AD5F-4A4C-B613-0AE08F51E17A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {775C1714-AD5F-4A4C-B613-0AE08F51E17A}.Release|Any CPU.Build.0 = Release|Any CPU - {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Release|Any CPU.Build.0 = Release|Any CPU - {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Release|Any CPU.Build.0 = Release|Any CPU - {14C55B44-2063-4891-98BE-8184CAB1BE87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14C55B44-2063-4891-98BE-8184CAB1BE87}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14C55B44-2063-4891-98BE-8184CAB1BE87}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14C55B44-2063-4891-98BE-8184CAB1BE87}.Release|Any CPU.Build.0 = Release|Any CPU - {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Release|Any CPU.Build.0 = Release|Any CPU - {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7} = {443E0B8D-9AD0-436E-A331-E8CC12965F07} - {38C1F619-3E1E-4784-9833-E8A2AA95CDAE} = {BA56455D-4AEA-45FC-A569-027A68A76BA6} - {14C55B44-2063-4891-98BE-8184CAB1BE87} = {443E0B8D-9AD0-436E-A331-E8CC12965F07} - {C58701B0-D8E3-4B68-A7DE-8524C95F86C0} = {443E0B8D-9AD0-436E-A331-E8CC12965F07} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1C4EE83B-632A-4929-8C96-38F14254229E} - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = FsAutoComplete\FsAutoComplete.fsproj - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.452 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{1BE8AF57-B314-4C92-82A9-64CD9B7A4990}" + ProjectSection(SolutionItems) = preProject + paket.dependencies = paket.dependencies + paket.lock = paket.lock + EndProjectSection +EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FsAutoComplete", "src\FsAutoComplete\FsAutoComplete.fsproj", "{B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}" +EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FsAutoComplete.Core", "src\FsAutoComplete.Core\FsAutoComplete.Core.fsproj", "{4E4786F3-4566-44E1-8787-91790007F0F6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{443E0B8D-9AD0-436E-A331-E8CC12965F07}" +EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FsAutoComplete.Tests.Lsp", "test\FsAutoComplete.Tests.Lsp\FsAutoComplete.Tests.Lsp.fsproj", "{6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BA56455D-4AEA-45FC-A569-027A68A76BA6}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsAutoComplete.Logging", "src\FsAutoComplete.Logging\FsAutoComplete.Logging.fsproj", "{38C1F619-3E1E-4784-9833-E8A2AA95CDAE}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "OptionAnalyzer", "test\OptionAnalyzer\OptionAnalyzer.fsproj", "{14C55B44-2063-4891-98BE-8184CAB1BE87}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsAutoComplete.DependencyManager.Dummy", "test\FsAutoComplete.DependencyManager.Dummy\FsAutoComplete.DependencyManager.Dummy.fsproj", "{C58701B0-D8E3-4B68-A7DE-8524C95F86C0}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "benchmarks", "benchmarks\benchmarks.fsproj", "{0CD029D8-B39E-4CBE-A190-C84A7A811180}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsAutoComplete.Tests.TestExplorer", "test\FsAutoComplete.Tests.TestExplorer\FsAutoComplete.Tests.TestExplorer.fsproj", "{C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Debug|x64.ActiveCfg = Debug|Any CPU + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Debug|x64.Build.0 = Debug|Any CPU + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Debug|x86.ActiveCfg = Debug|Any CPU + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Debug|x86.Build.0 = Debug|Any CPU + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Release|Any CPU.Build.0 = Release|Any CPU + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Release|x64.ActiveCfg = Release|Any CPU + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Release|x64.Build.0 = Release|Any CPU + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Release|x86.ActiveCfg = Release|Any CPU + {B6AB4EF3-8F60-41A1-AB0C-851A6DEB169E}.Release|x86.Build.0 = Release|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Debug|x64.ActiveCfg = Debug|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Debug|x64.Build.0 = Debug|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Debug|x86.Build.0 = Debug|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Release|Any CPU.Build.0 = Release|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Release|x64.ActiveCfg = Release|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Release|x64.Build.0 = Release|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Release|x86.ActiveCfg = Release|Any CPU + {4E4786F3-4566-44E1-8787-91790007F0F6}.Release|x86.Build.0 = Release|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Debug|x64.ActiveCfg = Debug|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Debug|x64.Build.0 = Debug|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Debug|x86.ActiveCfg = Debug|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Debug|x86.Build.0 = Debug|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Release|Any CPU.Build.0 = Release|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Release|x64.ActiveCfg = Release|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Release|x64.Build.0 = Release|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Release|x86.ActiveCfg = Release|Any CPU + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7}.Release|x86.Build.0 = Release|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Debug|x64.ActiveCfg = Debug|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Debug|x64.Build.0 = Debug|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Debug|x86.ActiveCfg = Debug|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Debug|x86.Build.0 = Debug|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Release|Any CPU.Build.0 = Release|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Release|x64.ActiveCfg = Release|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Release|x64.Build.0 = Release|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Release|x86.ActiveCfg = Release|Any CPU + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE}.Release|x86.Build.0 = Release|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Debug|x64.ActiveCfg = Debug|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Debug|x64.Build.0 = Debug|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Debug|x86.ActiveCfg = Debug|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Debug|x86.Build.0 = Debug|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Release|Any CPU.Build.0 = Release|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Release|x64.ActiveCfg = Release|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Release|x64.Build.0 = Release|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Release|x86.ActiveCfg = Release|Any CPU + {14C55B44-2063-4891-98BE-8184CAB1BE87}.Release|x86.Build.0 = Release|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Debug|x64.ActiveCfg = Debug|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Debug|x64.Build.0 = Debug|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Debug|x86.ActiveCfg = Debug|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Debug|x86.Build.0 = Debug|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Release|Any CPU.Build.0 = Release|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Release|x64.ActiveCfg = Release|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Release|x64.Build.0 = Release|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Release|x86.ActiveCfg = Release|Any CPU + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0}.Release|x86.Build.0 = Release|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Debug|x64.ActiveCfg = Debug|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Debug|x64.Build.0 = Debug|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Debug|x86.ActiveCfg = Debug|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Debug|x86.Build.0 = Debug|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Release|Any CPU.Build.0 = Release|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Release|x64.ActiveCfg = Release|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Release|x64.Build.0 = Release|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Release|x86.ActiveCfg = Release|Any CPU + {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Release|x86.Build.0 = Release|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Debug|x64.ActiveCfg = Debug|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Debug|x64.Build.0 = Debug|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Debug|x86.Build.0 = Debug|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Release|Any CPU.Build.0 = Release|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Release|x64.ActiveCfg = Release|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Release|x64.Build.0 = Release|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Release|x86.ActiveCfg = Release|Any CPU + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {6FE2E08A-9F3F-4C7C-B190-77C46AE147D7} = {443E0B8D-9AD0-436E-A331-E8CC12965F07} + {38C1F619-3E1E-4784-9833-E8A2AA95CDAE} = {BA56455D-4AEA-45FC-A569-027A68A76BA6} + {14C55B44-2063-4891-98BE-8184CAB1BE87} = {443E0B8D-9AD0-436E-A331-E8CC12965F07} + {C58701B0-D8E3-4B68-A7DE-8524C95F86C0} = {443E0B8D-9AD0-436E-A331-E8CC12965F07} + {C6CE23AF-EDCE-49ED-AC2F-8AD595C30CD8} = {443E0B8D-9AD0-436E-A331-E8CC12965F07} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1C4EE83B-632A-4929-8C96-38F14254229E} + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = FsAutoComplete\FsAutoComplete.fsproj + EndGlobalSection +EndGlobal diff --git a/paket.dependencies b/paket.dependencies index 23f193530..10077961a 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -44,6 +44,7 @@ nuget Microsoft.NET.Test.Sdk >= 17.4 nuget Dotnet.ReproducibleBuilds copy_local:true nuget Ionide.KeepAChangelog.Tasks copy_local: true +nuget Expecto ~> 10 nuget Expecto.Diff nuget YoloDev.Expecto.TestSdk nuget AltCover @@ -58,3 +59,8 @@ nuget CommunityToolkit.HighPerformance nuget System.Security.Cryptography.Pkcs nuget System.Net.Http nuget System.Text.RegularExpressions + + +## Test Explorer +nuget Microsoft.TestPlatform.TranslationLayer +nuget Microsoft.TestPlatform.ObjectModel \ No newline at end of file diff --git a/paket.lock b/paket.lock index 7aba40c53..5fd5f6626 100644 --- a/paket.lock +++ b/paket.lock @@ -36,7 +36,7 @@ NUGET Serilog (>= 3.1.1) DiffPlex (1.7.2) - restriction: || (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) Dotnet.ReproducibleBuilds (1.2.25) - copy_local: true - Expecto (10.2.1) - restriction: || (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) + Expecto (10.2.3) FSharp.Core (>= 7.0.200) - restriction: || (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) Mono.Cecil (>= 0.11.4 < 1.0) - restriction: || (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0)) Expecto.Diff (10.2.1) @@ -400,6 +400,8 @@ NUGET Microsoft.TestPlatform.TestHost (17.12) - restriction: || (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1)) Microsoft.TestPlatform.ObjectModel (>= 17.12) - restriction: || (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1)) Newtonsoft.Json (>= 13.0.1) - restriction: || (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) (&& (== netstandard2.1) (>= netcoreapp3.1)) + Microsoft.TestPlatform.TranslationLayer (17.13) + NETStandard.Library (>= 2.0) Microsoft.VisualStudio.SolutionPersistence (1.0.28) - restriction: || (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net8.0)) (&& (== netstandard2.1) (>= net8.0)) Microsoft.VisualStudio.Threading (17.12.19) Microsoft.Bcl.AsyncInterfaces (>= 6.0) - restriction: || (&& (== net8.0) (>= net472)) (&& (== net8.0) (< net6.0)) (&& (== net9.0) (>= net472)) (&& (== net9.0) (< net6.0)) (== netstandard2.0) (== netstandard2.1) @@ -423,6 +425,8 @@ NUGET Microsoft.VisualStudio.Validation (>= 17.8.8) System.IO.Pipelines (>= 8.0) System.Runtime.CompilerServices.Unsafe (>= 6.0) - restriction: || (&& (== net8.0) (< net6.0)) (&& (== net8.0) (< netstandard2.1)) (&& (== net9.0) (< net6.0)) (&& (== net9.0) (< netstandard2.1)) (== netstandard2.0) (== netstandard2.1) + NETStandard.Library (2.0.3) + Microsoft.NETCore.Platforms (>= 1.1) Newtonsoft.Json (13.0.3) Nuget.Frameworks (6.12.1) - copy_local: false OpenTelemetry (1.10) diff --git a/src/FsAutoComplete.Core/FsAutoComplete.Core.fsproj b/src/FsAutoComplete.Core/FsAutoComplete.Core.fsproj index 04812cf9f..186db15fe 100644 --- a/src/FsAutoComplete.Core/FsAutoComplete.Core.fsproj +++ b/src/FsAutoComplete.Core/FsAutoComplete.Core.fsproj @@ -3,7 +3,8 @@ net8.0 net8.0;net9.0 false - $(NoWarn);FS0057 + $(NoWarn);FS0057 + @@ -18,6 +19,8 @@ + + @@ -64,4 +67,4 @@ - + \ No newline at end of file diff --git a/src/FsAutoComplete.Core/TestServer.fs b/src/FsAutoComplete.Core/TestServer.fs new file mode 100644 index 000000000..961247503 --- /dev/null +++ b/src/FsAutoComplete.Core/TestServer.fs @@ -0,0 +1,88 @@ +namespace FsAutoComplete.TestServer + +open System + +type TestFileRange = { StartLine: int; EndLine: int } + +type TestItem = + { + FullName: string + DisplayName: string + /// Identifies the test adapter that ran the tests + /// Example: executor://xunit/VsTestRunner2/netcoreapp + /// Used for determining the test library, which effects how tests names are broken down + ExecutorUri: string + ProjectFilePath: string + TargetFramework: string + CodeFilePath: string option + CodeLocationRange: TestFileRange option + } + +module TestItem = + let ofVsTestCase + (projFilePath: string) + (targetFramework: string) + (testCase: Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase) + : TestItem = + { FullName = testCase.FullyQualifiedName + DisplayName = testCase.DisplayName + ExecutorUri = testCase.ExecutorUri |> string + ProjectFilePath = projFilePath + TargetFramework = targetFramework + CodeFilePath = Some testCase.CodeFilePath + CodeLocationRange = + Some + { StartLine = testCase.LineNumber + EndLine = testCase.LineNumber } } + + let tryTestCaseToDTO + (projectLookup: string -> Ionide.ProjInfo.Types.ProjectOptions option) + (testCase: Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase) + : TestItem option = + match projectLookup testCase.Source with + | None -> None // this should never happen. We pass VsTest the list of executables to test, so all the possible sources should be known to us + | Some project -> ofVsTestCase project.ProjectFileName project.TargetFramework testCase |> Some + +[] +type TestOutcome = + | Failed = 0 + | Passed = 1 + | Skipped = 2 + | None = 3 + | NotFound = 4 + +module TestOutcome = + type VSTestOutcome = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestOutcome + + let ofVSTestOutcome (vsTestOutcome: VSTestOutcome) = + match vsTestOutcome with + | VSTestOutcome.Passed -> TestOutcome.Passed + | VSTestOutcome.Failed -> TestOutcome.Failed + | VSTestOutcome.Skipped -> TestOutcome.Skipped + | VSTestOutcome.NotFound -> TestOutcome.NotFound + | VSTestOutcome.None -> TestOutcome.None + | _ -> TestOutcome.None + +type TestResult = + { TestItem: TestItem + Outcome: TestOutcome + ErrorMessage: string option + ErrorStackTrace: string option + AdditionalOutput: string option + Duration: TimeSpan } + +module TestResult = + type VSTestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult + + let ofVsTestResult (projFilePath: string) (targetFramework: string) (vsTestResult: VSTestResult) : TestResult = + let stringToOption (text: string) = if String.IsNullOrEmpty(text) then None else Some text + + { Outcome = TestOutcome.ofVSTestOutcome vsTestResult.Outcome + ErrorMessage = vsTestResult.ErrorMessage |> stringToOption + ErrorStackTrace = vsTestResult.ErrorStackTrace |> stringToOption + AdditionalOutput = + match vsTestResult.Messages |> Seq.toList with + | [] -> None + | messages -> messages |> List.map _.Text |> String.concat Environment.NewLine |> Some + Duration = vsTestResult.Duration + TestItem = TestItem.ofVsTestCase projFilePath targetFramework vsTestResult.TestCase } diff --git a/src/FsAutoComplete.Core/VSTestWrapper.fs b/src/FsAutoComplete.Core/VSTestWrapper.fs new file mode 100644 index 000000000..20ab6ca96 --- /dev/null +++ b/src/FsAutoComplete.Core/VSTestWrapper.fs @@ -0,0 +1,191 @@ +namespace FsAutoComplete.TestServer + +open System + +module VSTestWrapper = + open Microsoft.TestPlatform.VsTestConsole.TranslationLayer + open Microsoft.VisualStudio.TestPlatform.ObjectModel + open Microsoft.VisualStudio.TestPlatform.ObjectModel.Client + open Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging + open Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces + + type TestProjectDll = string + + type TestDiscoveryUpdate = + | Progress of TestCase list + | LogMessage of TestMessageLevel * string + + type private TestDiscoveryHandler(notifyDiscoveryProgress: TestDiscoveryUpdate -> unit) = + + member val DiscoveredTests: TestCase ResizeArray = ResizeArray() with get, set + + interface ITestDiscoveryEventsHandler with + member this.HandleDiscoveredTests(discoveredTestCases: System.Collections.Generic.IEnumerable) : unit = + if (not << isNull) discoveredTestCases then + this.DiscoveredTests.AddRange(discoveredTestCases) + notifyDiscoveryProgress (discoveredTestCases |> List.ofSeq |> Progress) + + member this.HandleDiscoveryComplete + (_totalTests: int64, lastChunk: System.Collections.Generic.IEnumerable, _isAborted: bool) + : unit = + if (not << isNull) lastChunk then + this.DiscoveredTests.AddRange(lastChunk) + notifyDiscoveryProgress (lastChunk |> List.ofSeq |> Progress) + + member this.HandleLogMessage(level: TestMessageLevel, message: string) : unit = + notifyDiscoveryProgress (LogMessage(level, message)) + + member this.HandleRawMessage(_rawMessage: string) : unit = () + + type ProcessId = int + type DidDebuggerAttach = bool + + type TestRunUpdate = + | Progress of TestRunChangedEventArgs + | LogMessage of TestMessageLevel * string + + type private TestRunHandler(notifyTestRunProgress: TestRunUpdate -> unit) = + + member val TestResults: TestResult ResizeArray = ResizeArray() with get, set + + interface ITestRunEventsHandler with + member _.HandleLogMessage(level: TestMessageLevel, message: string) : unit = + notifyTestRunProgress (LogMessage(level, message)) + + member _.HandleRawMessage(_rawMessage: string) : unit = () + + member this.HandleTestRunComplete + ( + _testRunCompleteArgs: TestRunCompleteEventArgs, + lastChunkArgs: TestRunChangedEventArgs, + _runContextAttachments: System.Collections.Generic.ICollection, + _executorUris: System.Collections.Generic.ICollection + ) : unit = + if ((not << isNull) lastChunkArgs && (not << isNull) lastChunkArgs.NewTestResults) then + this.TestResults.AddRange(lastChunkArgs.NewTestResults) + notifyTestRunProgress (Progress lastChunkArgs) + + member this.HandleTestRunStatsChange(testRunChangedArgs: TestRunChangedEventArgs) : unit = + if + ((not << isNull) testRunChangedArgs + && (not << isNull) testRunChangedArgs.NewTestResults) + then + this.TestResults.AddRange(testRunChangedArgs.NewTestResults) + notifyTestRunProgress (Progress testRunChangedArgs) + + member _.LaunchProcessWithDebuggerAttached(_testProcessStartInfo: TestProcessStartInfo) : int = + raise (System.NotImplementedException()) + + type private TestHostLauncher(isDebug: bool, onAttachDebugger: ProcessId -> DidDebuggerAttach) = + // IMPORTANT: RunTestsWithCustomTestHost says it takes an ITestHostLauncher, but it actually calls a method that is only available on ITestHostLauncher3 + + interface ITestHostLauncher3 with + member _.IsDebug: bool = isDebug + + member _.LaunchTestHost(_defaultTestHostStartInfo: TestProcessStartInfo) : int = raise (NotImplementedException()) + + member _.LaunchTestHost + (_defaultTestHostStartInfo: TestProcessStartInfo, _cancellationToken: Threading.CancellationToken) + : int = + raise (NotImplementedException()) + + member _.AttachDebuggerToProcess + (attachDebuggerInfo: AttachDebuggerInfo, _cancellationToken: Threading.CancellationToken) + : bool = + onAttachDebugger attachDebuggerInfo.ProcessId + + member _.AttachDebuggerToProcess(pid: int) : bool = onAttachDebugger pid + + member _.AttachDebuggerToProcess(pid: int, _cancellationToken: Threading.CancellationToken) : bool = + onAttachDebugger pid + + + module TestPlatformOptions = + let withTestCaseFilter (options: TestPlatformOptions) filterExpression = options.TestCaseFilter <- filterExpression + + module private RunSettings = + let defaultRunSettings = + " + + False + +" + + let discoverTestsAsync + (vstestPath: string) + (onDiscoveryProgress: TestDiscoveryUpdate -> unit) + (sources: TestProjectDll list) + : Async = + async { + let consoleParams = ConsoleParameters() + let vstest = new VsTestConsoleWrapper(vstestPath, consoleParams) + let discoveryHandler = TestDiscoveryHandler(onDiscoveryProgress) + + use! _onCancel = Async.OnCancel(fun () -> vstest.CancelDiscovery()) + + vstest.DiscoverTests(sources, null, discoveryHandler) + return discoveryHandler.DiscoveredTests |> List.ofSeq + } + + /// onAttachDebugger assumes that the debugger is attached when the method returns. The test project will continue execution as soon as attachDebugger returns + let runTestsAsync + (vstestPath: string) + (onTestRunProgress: TestRunUpdate -> unit) + (onAttachDebugger: ProcessId -> DidDebuggerAttach) + (sources: TestProjectDll list) + (testCaseFilter: string option) + (shouldDebug: bool) + : Async = + async { + let consoleParams = ConsoleParameters() + let vstest = new VsTestConsoleWrapper(vstestPath, consoleParams) + let runHandler = TestRunHandler(onTestRunProgress) + let runSettings = RunSettings.defaultRunSettings + + let options = new TestPlatformOptions() + testCaseFilter |> Option.iter (TestPlatformOptions.withTestCaseFilter options) + + use! _cancel = Async.OnCancel(fun () -> vstest.CancelTestRun()) + + if shouldDebug then + let hostLauncher = TestHostLauncher(shouldDebug, onAttachDebugger) + vstest.RunTestsWithCustomTestHost(sources, runSettings, options, runHandler, hostLauncher) + else + vstest.RunTests(sources, runSettings, options, runHandler) + + return runHandler.TestResults |> List.ofSeq + } + + open System.IO + + let tryFindVsTestFromDotnetRoot (dotnetRoot: string) (workspaceRoot: string option) : Result = + let cwd = + defaultArg workspaceRoot System.Environment.CurrentDirectory |> DirectoryInfo + + let dotnetBinary = + if dotnetRoot |> Directory.Exists then + if + System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( + System.Runtime.InteropServices.OSPlatform.Windows + ) + then + FileInfo(Path.Combine(dotnetRoot, "dotnet.exe")) + else + FileInfo(Path.Combine(dotnetRoot, "dotnet")) + else + dotnetRoot |> FileInfo + + match Ionide.ProjInfo.SdkDiscovery.versionAt cwd dotnetBinary with + | Ok sdkVersion -> + let sdks = Ionide.ProjInfo.SdkDiscovery.sdks dotnetBinary + + match sdks |> Array.tryFind (fun sdk -> sdk.Version = sdkVersion) with + | Some sdk -> + let vstestBinary = Path.Combine(sdk.Path.FullName, "vstest.console.dll") |> FileInfo + + if vstestBinary.Exists then + Ok vstestBinary + else + Error $"Found the correct dotnet sdk, but vstest was not at the expected sub-path: {vstestBinary.FullName}" + | None -> Error $"Couldn't find the install location for dotnet sdk version: {sdkVersion}" + | Error _ -> Error $"Couldn't identify the dotnet version for working directory: {cwd.FullName}" diff --git a/src/FsAutoComplete.Core/paket.references b/src/FsAutoComplete.Core/paket.references index 4c77fac04..deecc0716 100644 --- a/src/FsAutoComplete.Core/paket.references +++ b/src/FsAutoComplete.Core/paket.references @@ -17,3 +17,6 @@ FSharp.Analyzers.SDK Ionide.Analyzers FSharp.Analyzers.Build Serilog.Extensions.Logging + +Microsoft.TestPlatform.ObjectModel +Microsoft.TestPlatform.TranslationLayer \ No newline at end of file diff --git a/src/FsAutoComplete/CommandResponse.fs b/src/FsAutoComplete/CommandResponse.fs index bfc37efe7..b71d04ae7 100644 --- a/src/FsAutoComplete/CommandResponse.fs +++ b/src/FsAutoComplete/CommandResponse.fs @@ -702,3 +702,17 @@ module CommandResponse = PrecedingNonPipeExprLine = pnp }) serialize { Kind = "pipelineHint"; Data = ctn } + + + + type DiscoverTestsResponse = TestServer.TestItem list + + let discoverTests (serialize: Serializer) (content: DiscoverTestsResponse) = + serialize + { Kind = "discoverTests" + Data = content } + + let runTests (serialize: Serializer) (content: TestServer.TestResult list) = + serialize + { Kind = "runTests" + Data = content |> Array.ofList } diff --git a/src/FsAutoComplete/CommandResponse.fsi b/src/FsAutoComplete/CommandResponse.fsi index b68c16035..0868e154f 100644 --- a/src/FsAutoComplete/CommandResponse.fsi +++ b/src/FsAutoComplete/CommandResponse.fsi @@ -261,3 +261,8 @@ module CommandResponse = val compile: serialize: Serializer -> errors: #FSharpDiagnostic array * code: int -> string val fsharpLiterate: serialize: Serializer -> content: string -> string val pipelineHint: serialize: Serializer -> content: (int * int option * string list)[] -> string + + type DiscoverTestsResponse = TestServer.TestItem list + + val discoverTests: serialize: Serializer -> content: DiscoverTestsResponse -> string + val runTests: serialize: Serializer -> content: TestServer.TestResult list -> string diff --git a/src/FsAutoComplete/LspHelpers.fs b/src/FsAutoComplete/LspHelpers.fs index 5baee17b4..9d7ffa24b 100644 --- a/src/FsAutoComplete/LspHelpers.fs +++ b/src/FsAutoComplete/LspHelpers.fs @@ -612,6 +612,22 @@ type TestDetectedNotification = { File: string Tests: TestAdapter.TestAdapterEntry array } +type TestRunRequest = + { LimitToProjects: FilePath list option + TestCaseFilter: string option + AttachDebugger: bool } + +type TestLogMessage = { Level: string; Message: string } + +type TestDiscoveryUpdateNotification = + { Tests: TestServer.TestItem array + TestLogs: TestLogMessage array } + +type TestRunProgress = + { TestLogs: TestLogMessage array + TestResults: TestServer.TestResult array + ActiveTests: TestServer.TestItem array } + type ProjectParms = { /// Project file to compile diff --git a/src/FsAutoComplete/LspHelpers.fsi b/src/FsAutoComplete/LspHelpers.fsi index 0ba878d42..25b93cefe 100644 --- a/src/FsAutoComplete/LspHelpers.fsi +++ b/src/FsAutoComplete/LspHelpers.fsi @@ -198,6 +198,22 @@ type TestDetectedNotification = { File: string Tests: TestAdapter.TestAdapterEntry array } +type TestRunRequest = + { LimitToProjects: FilePath list option + TestCaseFilter: string option + AttachDebugger: bool } + +type TestLogMessage = { Level: string; Message: string } + +type TestDiscoveryUpdateNotification = + { Tests: TestServer.TestItem array + TestLogs: TestLogMessage array } + +type TestRunProgress = + { TestLogs: TestLogMessage array + TestResults: TestServer.TestResult array + ActiveTests: TestServer.TestItem array } + type ProjectParms = { /// Project file to compile @@ -465,7 +481,6 @@ type FSharpInlayHintsRequest = { TextDocument: TextDocumentIdentifier Range: Range } - [] module Extensions = diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 272dc44f4..4f3d90917 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -3056,6 +3056,52 @@ type AdaptiveFSharpLspServer return! returnException e logCfg } + override this.TestDiscoverTests() : Async> = + asyncResult { + use trace = fsacActivitySource.StartActivityForType(thisType) + + try + logger.info (Log.setMessage "TestDiscoverTests Request") + + let! testDTOs = + state.DiscoverTests() + |> AsyncResult.mapError (fun msg -> JsonRpc.Error.InternalError msg) + + return Some { Content = CommandResponse.discoverTests FsAutoComplete.JsonSerializer.writeJson testDTOs } + with e -> + trace |> Tracing.recordException e + + let logCfg = Log.setMessage "TestDiscoverTests Request Errored" + + return! returnException e logCfg + } + + override this.TestRunTests(p: TestRunRequest) : Async> = + asyncResult { + let tags = [ "TestRunRequest", box p ] + use trace = fsacActivitySource.StartActivityForType(thisType, tags = tags) + + try + logger.info ( + Log.setMessage "TestRunTests Request: {params}" + >> Log.addContextDestructured "params" p + ) + + let! testDTOs = + state.RunTests p.LimitToProjects p.TestCaseFilter p.AttachDebugger + |> AsyncResult.mapError (fun msg -> JsonRpc.Error.InternalError msg) + + return Some { Content = CommandResponse.runTests FsAutoComplete.JsonSerializer.writeJson testDTOs } + with e -> + trace |> Tracing.recordException e + + let logCfg = + Log.setMessage "TestRunTests Request Errored {p}" + >> Log.addContextDestructured "p" p + + return! returnException e logCfg + } + override x.Dispose() = disposables.Dispose() member this.WindowWorkDoneProgressCancel(param: WorkDoneProgressCancelParams) : Async = @@ -3096,6 +3142,7 @@ type AdaptiveFSharpLspServer member this.Progress(_arg1: ProgressParams) : Async = ignoreNotification member this.SetTrace(_arg1: SetTraceParams) : Async = ignoreNotification + module AdaptiveFSharpLspServer = open StreamJsonRpc @@ -3185,6 +3232,8 @@ module AdaptiveFSharpLspServer = |> Map.add "fsproj/addExistingFile" (serverRequestHandling (fun s p -> s.FsProjAddExistingFile(p))) |> Map.add "fsproj/renameFile" (serverRequestHandling (fun s p -> s.FsProjRenameFile(p))) |> Map.add "fsproj/removeFile" (serverRequestHandling (fun s p -> s.FsProjRemoveFile(p))) + |> Map.add "test/discoverTests" (serverRequestHandling (fun s _ -> s.TestDiscoverTests())) + |> Map.add "test/runTests" (serverRequestHandling (fun s p -> s.TestRunTests(p))) let adaptiveServer lspClient = let loader = workspaceLoaderFactory toolsPath diff --git a/src/FsAutoComplete/LspServers/AdaptiveServerState.fs b/src/FsAutoComplete/LspServers/AdaptiveServerState.fs index 0976b38f3..dc8405535 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveServerState.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveServerState.fs @@ -156,6 +156,13 @@ type FindFirstProject() = $"Couldn't find a corresponding project for {sourceFile}. \n Projects include {allProjects}. \nHave the projects loaded yet or have you tried restoring your project/solution?") +module TestProjectHelpers = + let isTestProject (project: Types.ProjectOptions) = + let testProjectIndicators = + set [ "Microsoft.TestPlatform.TestHost"; "Microsoft.NET.Test.Sdk" ] + + project.PackageReferences + |> List.exists (fun pr -> Set.contains pr.Name testProjectIndicators) type AdaptiveState ( @@ -801,6 +808,7 @@ type AdaptiveState { File = Path.LocalPathToUri file Tests = tests |> Array.map map } |> lspClient.NotifyTestDetected + with ex -> logger.error ( Log.setMessage "Exception while handling command event {evt}: {ex}" @@ -2567,6 +2575,153 @@ type AdaptiveState member x.GlyphToSymbolKind = glyphToSymbolKind |> AVal.force + member state.DiscoverTests() = + + asyncResult { + let! vstestBinary = TestServer.VSTestWrapper.tryFindVsTestFromDotnetRoot state.Config.DotNetRoot state.RootPath + + let! projects = projectOptions |> AsyncAVal.forceAsync + + let testProjects = + projects.ToValueList() |> List.filter TestProjectHelpers.isTestProject + + let testProjectBinaries = testProjects |> List.map _.TargetPath + + if testProjects |> List.isEmpty then + let message = "No test projects found. Make sure you've restored your projects" + + do! + lspClient.WindowShowMessage( + { Type = MessageType.Error + Message = message } + ) + + return! (Error message) + elif testProjectBinaries |> List.filter File.Exists |> List.isEmpty then + let message = + "No binaries found for test projects. Make sure you've built your projects" + + do! + lspClient.WindowShowMessage( + { Type = MessageType.Error + Message = message } + ) + + return! (Error message) + + let tryTestCasesToDTOs testCases = + let projectLookup = testProjects |> Seq.map (fun p -> p.TargetPath, p) |> Map.ofSeq + + testCases + |> List.choose (TestServer.TestItem.tryTestCaseToDTO projectLookup.TryFind) + + let onDiscoveryProgress (update: TestServer.VSTestWrapper.TestDiscoveryUpdate) = + let dto = + match update with + | TestServer.VSTestWrapper.TestDiscoveryUpdate.Progress tests -> + { Tests = tests |> tryTestCasesToDTOs |> Array.ofList + TestLogs = [||] } + | TestServer.VSTestWrapper.TestDiscoveryUpdate.LogMessage(level, message) -> + { Tests = [||] + TestLogs = + [| { Message = message + Level = string level } |] } + + + lspClient.NotifyTestDiscoveryUpdate(dto) |> Async.RunSynchronously + + let! testCases = + TestServer.VSTestWrapper.discoverTestsAsync vstestBinary.FullName onDiscoveryProgress testProjectBinaries + + let testDTOs: TestServer.TestItem list = testCases |> tryTestCasesToDTOs + + return testDTOs + } + + member state.RunTests (limitToProjects: FilePath list option) (testCaseFilter: string option) (shouldDebug: bool) = + asyncResult { + let! vstestBinary = TestServer.VSTestWrapper.tryFindVsTestFromDotnetRoot state.Config.DotNetRoot state.RootPath + + let! projects = projectOptions |> AsyncAVal.forceAsync + + let testProjects = + projects.ToValueList() |> List.filter TestProjectHelpers.isTestProject + + let filteredTestProjects = + match limitToProjects with + | None -> testProjects + | Some specifiedProjects -> + let specifiedProjectsSet = specifiedProjects |> List.map Path.GetFullPath |> set + testProjects |> List.filter (_.ProjectFileName >> specifiedProjectsSet.Contains) + + let testProjectBinaries = filteredTestProjects |> List.map _.TargetPath + + let projectsByBinaryPath = + testProjects |> Seq.map (fun p -> p.TargetPath, p) |> Map.ofSeq + + let tryTestResultsToDTOs testCases = + let tryTestResultToDTO + (projectLookup: Map) + (testResult: Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult) + : TestServer.TestResult option = + match projectLookup |> Map.tryFind testResult.TestCase.Source with + | None -> None // this should never happen. We pass VsTest the list of executables to test, so all the possible sources should be known to us + | Some project -> + TestServer.TestResult.ofVsTestResult project.ProjectFileName project.TargetFramework testResult + |> Some + + testCases |> List.choose (tryTestResultToDTO projectsByBinaryPath) + + use tokenSource = new CancellationTokenSource() + + use! _onCancel = Async.OnCancel(fun _ -> tokenSource.Cancel()) + + let onTestRunProgress (runUpdate: TestServer.VSTestWrapper.TestRunUpdate) = + let dto = + match runUpdate with + | TestServer.VSTestWrapper.TestRunUpdate.Progress progress -> + { TestLogs = [||] + TestResults = progress.NewTestResults |> List.ofSeq |> tryTestResultsToDTOs |> Array.ofSeq + ActiveTests = + progress.ActiveTests + |> Seq.choose (TestServer.TestItem.tryTestCaseToDTO projectsByBinaryPath.TryFind) + |> Array.ofSeq } + | TestServer.VSTestWrapper.TestRunUpdate.LogMessage(level, message) -> + { TestLogs = + [| { Message = message + Level = string level } |] + TestResults = [||] + ActiveTests = [||] } + + Async.RunSynchronously(async { do! lspClient.NotifyTestRunUpdate(dto) }, cancellationToken = tokenSource.Token) + + let onAttachDebugger (processId: int) : bool = + let result = + Async.RunSynchronously(lspClient.AttachDebuggerForTestRun(processId), cancellationToken = tokenSource.Token) + + match result with + | Ok didAttach -> didAttach + | Error err -> + logger.warn ( + Log.setMessageI + $"Failed to attach debugger for test run with Process Id: {processId}; Error Code: {err.Code}; Error message: {err.Message}" + ) + + false + + let! testResults = + TestServer.VSTestWrapper.runTestsAsync + vstestBinary.FullName + onTestRunProgress + onAttachDebugger + testProjectBinaries + testCaseFilter + shouldDebug + + let resultDtos = testResults |> tryTestResultsToDTOs + return resultDtos + } + member x.CancelServerProgress(progressToken: ProgressToken) = progressLookup.Cancel progressToken diff --git a/src/FsAutoComplete/LspServers/AdaptiveServerState.fsi b/src/FsAutoComplete/LspServers/AdaptiveServerState.fsi index 9ee4f4a73..edff7ef19 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveServerState.fsi +++ b/src/FsAutoComplete/LspServers/AdaptiveServerState.fsi @@ -119,6 +119,8 @@ type AdaptiveState = member GetDeclarations: filename: string -> Async> member GetAllDeclarations: unit -> Async<(string * NavigationTopLevelDeclaration array) array> member GlyphToSymbolKind: (FSharpGlyph -> SymbolKind option) + member DiscoverTests: unit -> Async> + member RunTests: FilePath list option -> string option -> bool -> Async> /// /// Signals the server to cancel an operation that is associated with the given progress token. /// diff --git a/src/FsAutoComplete/LspServers/FSharpLspClient.fs b/src/FsAutoComplete/LspServers/FSharpLspClient.fs index 532298ed8..8b5cabd41 100644 --- a/src/FsAutoComplete/LspServers/FSharpLspClient.fs +++ b/src/FsAutoComplete/LspServers/FSharpLspClient.fs @@ -62,6 +62,17 @@ type FSharpLspClient(sendServerNotification: ClientNotificationSender, sendServe member __.NotifyTestDetected(p: TestDetectedNotification) = sendServerNotification "fsharp/testDetected" (box p) |> Async.Ignore + member __.NotifyTestDiscoveryUpdate(p: TestDiscoveryUpdateNotification) = + sendServerNotification "test/testDiscoveryUpdate" (box { Content = JsonSerializer.writeJson p }) + |> Async.Ignore + + member __.NotifyTestRunUpdate(p: TestRunProgress) = + sendServerNotification "test/testRunProgressUpdate" (box { Content = JsonSerializer.writeJson p }) + |> Async.Ignore + + member __.AttachDebuggerForTestRun(processId: int) : AsyncLspResult = + sendServerRequest.Send "test/processWaitingForDebugger" (box { Content = string processId }) + member x.CodeLensRefresh() = match x.ClientCapabilities with | Some { Workspace = Some { CodeLens = Some { RefreshSupport = Some true } } } -> diff --git a/src/FsAutoComplete/LspServers/FSharpLspClient.fsi b/src/FsAutoComplete/LspServers/FSharpLspClient.fsi index b41b37464..0576df043 100644 --- a/src/FsAutoComplete/LspServers/FSharpLspClient.fsi +++ b/src/FsAutoComplete/LspServers/FSharpLspClient.fsi @@ -32,6 +32,9 @@ type FSharpLspClient = member NotifyFileParsed: p: PlainNotification -> Async member NotifyDocumentAnalyzed: p: DocumentAnalyzedNotification -> Async member NotifyTestDetected: p: TestDetectedNotification -> Async + member NotifyTestDiscoveryUpdate: p: TestDiscoveryUpdateNotification -> Async + member NotifyTestRunUpdate: p: TestRunProgress -> Async + member AttachDebuggerForTestRun: processId: int -> AsyncLspResult member CodeLensRefresh: unit -> Async override WindowWorkDoneProgressCreate: WorkDoneProgressCreateParams -> AsyncLspResult member Progress: ProgressToken * 'Progress -> Async diff --git a/src/FsAutoComplete/LspServers/IFSharpLspServer.fs b/src/FsAutoComplete/LspServers/IFSharpLspServer.fs index 90309b7ed..00d61e196 100644 --- a/src/FsAutoComplete/LspServers/IFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/IFSharpLspServer.fs @@ -47,3 +47,5 @@ type IFSharpLspServer = abstract FsProjAddFile: DotnetFileRequest -> Async> abstract FsProjRemoveFile: DotnetFileRequest -> Async> abstract FsProjAddExistingFile: DotnetFileRequest -> Async> + abstract TestDiscoverTests: unit -> Async> + abstract TestRunTests: TestRunRequest -> Async> diff --git a/test/FsAutoComplete.Tests.Lsp/DotnetCli.fs b/test/FsAutoComplete.Tests.Lsp/DotnetCli.fs new file mode 100644 index 000000000..df5f4bc16 --- /dev/null +++ b/test/FsAutoComplete.Tests.Lsp/DotnetCli.fs @@ -0,0 +1,26 @@ +namespace FsAutoComplete.Tests.Lsp.Helpers + +module DotnetCli = + open System + + let private executeProcess (wd: string option) (processName: string) (processArgs: string) = + let psi = new Diagnostics.ProcessStartInfo(processName, processArgs) + psi.UseShellExecute <- false + psi.RedirectStandardOutput <- true + psi.RedirectStandardError <- true + psi.CreateNoWindow <- true + wd |> Option.iter (fun wd -> psi.WorkingDirectory <- wd) + let proc = Diagnostics.Process.Start(psi) + let output = new Text.StringBuilder() + let error = new Text.StringBuilder() + proc.OutputDataReceived.Add(fun args -> output.Append(args.Data) |> ignore) + proc.ErrorDataReceived.Add(fun args -> error.Append(args.Data) |> ignore) + proc.BeginErrorReadLine() + proc.BeginOutputReadLine() + proc.WaitForExit() + + {| ExitCode = proc.ExitCode + StdOut = output.ToString() + StdErr = error.ToString() |} + + let build path = executeProcess None "dotnet" $"build {path}" diff --git a/test/FsAutoComplete.Tests.Lsp/FsAutoComplete.Tests.Lsp.fsproj b/test/FsAutoComplete.Tests.Lsp/FsAutoComplete.Tests.Lsp.fsproj index 14c0002c5..8b0202113 100644 --- a/test/FsAutoComplete.Tests.Lsp/FsAutoComplete.Tests.Lsp.fsproj +++ b/test/FsAutoComplete.Tests.Lsp/FsAutoComplete.Tests.Lsp.fsproj @@ -1,61 +1,62 @@ - - - Exe - net8.0 - net8.0;net9.0 - false - LatestMajor - true - true - true + + + Exe + net8.0 + net8.0;net9.0 + false + LatestMajor + true + true + true - - $(NoWarn);FS0075 - $(OtherFlags) --test:GraphBasedChecking --test:DumpCheckingGraph - - - - - FsAutoComplete.fsproj - - - FsAutoComplete.Core.fsproj - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + in .net 6 the TestCases projects don't know about these flags --> + + $(NoWarn);FS0075 + $(OtherFlags) --test:GraphBasedChecking --test:DumpCheckingGraph + + + + + FsAutoComplete.fsproj + + + FsAutoComplete.Core.fsproj + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + project uses it's own set of Microsoft.Build dependencies which causes loading conflicts --> + + + + + \ No newline at end of file diff --git a/test/FsAutoComplete.Tests.Lsp/GoToTests.fs b/test/FsAutoComplete.Tests.Lsp/GoToTests.fs index b5bd4bb1a..2a2fa472b 100644 --- a/test/FsAutoComplete.Tests.Lsp/GoToTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/GoToTests.fs @@ -12,26 +12,7 @@ open Utils.Server open Utils.Utils open Utils.TextEdit open Helpers.Expecto.ShadowedTimeouts - -let executeProcess (wd: string) (processName: string) (processArgs: string) = - let psi = new Diagnostics.ProcessStartInfo(processName, processArgs) - psi.UseShellExecute <- false - psi.RedirectStandardOutput <- true - psi.RedirectStandardError <- true - psi.CreateNoWindow <- true - psi.WorkingDirectory <- wd - let proc = Diagnostics.Process.Start(psi) - let output = new Text.StringBuilder() - let error = new Text.StringBuilder() - proc.OutputDataReceived.Add(fun args -> output.Append(args.Data) |> ignore) - proc.ErrorDataReceived.Add(fun args -> error.Append(args.Data) |> ignore) - proc.BeginErrorReadLine() - proc.BeginOutputReadLine() - proc.WaitForExit() - - {| ExitCode = proc.ExitCode - StdOut = output.ToString() - StdErr = error.ToString() |} +open FsAutoComplete.Tests.Lsp.Helpers ///GoTo tests let private gotoTest state = @@ -40,7 +21,7 @@ let private gotoTest state = let path = Path.Combine(__SOURCE_DIRECTORY__, "TestCases", "GoToTests") let csharpPath = Path.Combine(__SOURCE_DIRECTORY__, "TestCases", "GoToCSharp") - let _buildInfo = executeProcess csharpPath "dotnet" "build" + let _buildInfo = DotnetCli.build csharpPath let! (server, event) = serverInitialize path defaultConfigDto state do! waitForWorkspaceFinishedParsing event diff --git a/test/FsAutoComplete.Tests.Lsp/Program.fs b/test/FsAutoComplete.Tests.Lsp/Program.fs index 6736ea79a..a495a7ff9 100644 --- a/test/FsAutoComplete.Tests.Lsp/Program.fs +++ b/test/FsAutoComplete.Tests.Lsp/Program.fs @@ -35,22 +35,28 @@ let testTimeout = // delay in ms between workspace start + stop notifications because the system goes too fast :-/ Environment.SetEnvironmentVariable("FSAC_WORKSPACELOAD_DELAY", "250") -let getEnvVarAsStr name = - Environment.GetEnvironmentVariable(name) - |> Option.ofObj +let getEnvVarAsStr name = Environment.GetEnvironmentVariable(name) |> Option.ofObj let (|EqIC|_|) (a: string) (b: string) = - if String.Equals(a, b, StringComparison.OrdinalIgnoreCase) then Some () else None + if String.Equals(a, b, StringComparison.OrdinalIgnoreCase) then + Some() + else + None let loaders = match getEnvVarAsStr "USE_WORKSPACE_LOADER" with - | Some (EqIC "WorkspaceLoader") -> [ "Ionide WorkspaceLoader", (fun toolpath -> WorkspaceLoader.Create(toolpath, FsAutoComplete.Core.ProjectLoader.globalProperties)) ] - | Some (EqIC "ProjectGraph") -> [ "MSBuild Project Graph WorkspaceLoader", (fun toolpath -> WorkspaceLoaderViaProjectGraph.Create(toolpath, FsAutoComplete.Core.ProjectLoader.globalProperties)) ] + | Some(EqIC "WorkspaceLoader") -> + [ "Ionide WorkspaceLoader", + (fun toolpath -> WorkspaceLoader.Create(toolpath, FsAutoComplete.Core.ProjectLoader.globalProperties)) ] + | Some(EqIC "ProjectGraph") -> + [ "MSBuild Project Graph WorkspaceLoader", + (fun toolpath -> + WorkspaceLoaderViaProjectGraph.Create(toolpath, FsAutoComplete.Core.ProjectLoader.globalProperties)) ] | _ -> - [ - "Ionide WorkspaceLoader", (fun toolpath -> WorkspaceLoader.Create(toolpath, FsAutoComplete.Core.ProjectLoader.globalProperties)) + [ "Ionide WorkspaceLoader", + (fun toolpath -> WorkspaceLoader.Create(toolpath, FsAutoComplete.Core.ProjectLoader.globalProperties)) // "MSBuild Project Graph WorkspaceLoader", (fun toolpath -> WorkspaceLoaderViaProjectGraph.Create(toolpath, FsAutoComplete.Core.ProjectLoader.globalProperties)) - ] + ] let adaptiveLspServerFactory toolsPath workspaceLoaderFactory sourceTextFactory = @@ -65,28 +71,22 @@ let mutable toolsPath = let compilers = match getEnvVarAsStr "USE_TRANSPARENT_COMPILER" with - | Some (EqIC "TransparentCompiler") -> ["TransparentCompiler", true ] - | Some (EqIC "BackgroundCompiler") -> [ "BackgroundCompiler", false ] - | _ -> - [ - "BackgroundCompiler", false - "TransparentCompiler", true - ] + | Some(EqIC "TransparentCompiler") -> [ "TransparentCompiler", true ] + | Some(EqIC "BackgroundCompiler") -> [ "BackgroundCompiler", false ] + | _ -> [ "BackgroundCompiler", false; "TransparentCompiler", true ] let lspTests = - testSequenced <| - testList + testSequenced + <| testList "lsp" [ for (loaderName, workspaceLoaderFactory) in loaders do testList $"{loaderName}" - [ - for (compilerName, useTransparentCompiler) in compilers do + [ for (compilerName, useTransparentCompiler) in compilers do testList $"{compilerName}" - [ - Templates.tests () + [ Templates.tests () let createServer () = adaptiveLspServerFactory toolsPath workspaceLoaderFactory sourceTextFactory useTransparentCompiler @@ -137,22 +137,20 @@ let lspTests = EmptyFileTests.tests createServer CallHierarchy.tests createServer diagnosticsTest createServer - ] ] ] + + TestExplorer.tests createServer ] ] ] /// Tests that do not require a LSP server -let generalTests = testList "general" [ - testList (nameof (Utils)) [ Utils.Tests.Utils.tests; Utils.Tests.TextEdit.tests ] - UtilsTests.allTests - InlayHintTests.explicitTypeInfoTests sourceTextFactory - FindReferences.tryFixupRangeTests sourceTextFactory -] +let generalTests = + testList + "general" + [ testList (nameof (Utils)) [ Utils.Tests.Utils.tests; Utils.Tests.TextEdit.tests ] + InlayHintTests.explicitTypeInfoTests sourceTextFactory + FindReferences.tryFixupRangeTests sourceTextFactory ] [] -let tests = testList "FSAC" [ - generalTests - lspTests - SnapshotTests.snapshotTests loaders toolsPath - ] +let tests = + testList "FSAC" [ generalTests; lspTests; SnapshotTests.snapshotTests loaders toolsPath ] open OpenTelemetry open OpenTelemetry.Resources @@ -165,18 +163,19 @@ open FsAutoComplete.Telemetry [] let main args = let serviceName = "FsAutoComplete.Tests.Lsp" + use traceProvider = let version = FsAutoComplete.Utils.Version.info().Version + Sdk .CreateTracerProviderBuilder() .AddSource(FsAutoComplete.Utils.Tracing.serviceName, Tracing.fscServiceName, serviceName) .SetResourceBuilder( - ResourceBuilder - .CreateDefault() - .AddService(serviceName = serviceName, serviceVersion = version) + ResourceBuilder.CreateDefault().AddService(serviceName = serviceName, serviceVersion = version) ) .AddOtlpExporter() .Build() + let outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}" @@ -281,11 +280,9 @@ let main args = use activitySource = new ActivitySource(serviceName) let cliArgs = - [ - CLIArguments.Printer(Expecto.Impl.TestPrinters.summaryWithLocationPrinter defaultConfig.printer) + [ CLIArguments.Printer(Expecto.Impl.TestPrinters.summaryWithLocationPrinter defaultConfig.printer) CLIArguments.Verbosity Expecto.Logging.LogLevel.Info - CLIArguments.Parallel - ] + CLIArguments.Parallel ] // let trace = traceProvider.GetTracer("FsAutoComplete.Tests.Lsp") // use span = trace.StartActiveSpan("runTests", SpanKind.Internal) use span = activitySource.StartActivity("runTests") diff --git a/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.RunResults/Program.fs b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.RunResults/Program.fs new file mode 100644 index 000000000..31dc4f735 --- /dev/null +++ b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.RunResults/Program.fs @@ -0,0 +1,4 @@ +module Program + +[] +let main _ = 0 diff --git a/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.RunResults/Tests.fs b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.RunResults/Tests.fs new file mode 100644 index 000000000..28e514dbe --- /dev/null +++ b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.RunResults/Tests.fs @@ -0,0 +1,30 @@ +module Tests + +open System +open Xunit + +[] +let ``My test`` () = + System.Console.WriteLine("Where do I show up in the results") + Assert.True(true) + +[] +let ``Fails`` () = Assert.True(false) + +[] +let ``Skipped`` () = Assert.True(true) + +[] +let ``Exception`` () : unit = failwith "Report as an exception" + +[] +let ``Expects environment variable`` () : unit = + Assert.Equal("Set me", Environment.GetEnvironmentVariable("dd586685-08f6-410c-a9f1-84530af117ab")) + + +module Nested = + [] + let ``Test 1`` () : unit = () + + [] + let ``Test 2`` () : unit = () diff --git a/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.RunResults/VSTest.XUnit.RunResults.fsproj b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.RunResults/VSTest.XUnit.RunResults.fsproj new file mode 100644 index 000000000..1a4224efd --- /dev/null +++ b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.RunResults/VSTest.XUnit.RunResults.fsproj @@ -0,0 +1,22 @@ + + + + net8.0 + false + false + + + + + + + + + + + + + + + + diff --git a/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.Tests/Program.fs b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.Tests/Program.fs new file mode 100644 index 000000000..31dc4f735 --- /dev/null +++ b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.Tests/Program.fs @@ -0,0 +1,4 @@ +module Program + +[] +let main _ = 0 diff --git a/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.Tests/Tests.fs b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.Tests/Tests.fs new file mode 100644 index 000000000..7f70fae19 --- /dev/null +++ b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.Tests/Tests.fs @@ -0,0 +1,8 @@ +module Tests + +open System +open Xunit + +[] +let ``My test`` () = + Assert.True(true) diff --git a/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.Tests/VSTest.XUnit.Tests.fsproj b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.Tests/VSTest.XUnit.Tests.fsproj new file mode 100644 index 000000000..1a4224efd --- /dev/null +++ b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/VSTest.XUnit.Tests/VSTest.XUnit.Tests.fsproj @@ -0,0 +1,22 @@ + + + + net8.0 + false + false + + + + + + + + + + + + + + + + diff --git a/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/paket.dependencies b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/paket.dependencies new file mode 100644 index 000000000..e69de29bb diff --git a/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/paket.lock b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/paket.lock new file mode 100644 index 000000000..d3f5a12fa --- /dev/null +++ b/test/FsAutoComplete.Tests.Lsp/SampleTestProjects/paket.lock @@ -0,0 +1 @@ + diff --git a/test/FsAutoComplete.Tests.Lsp/TestExplorerTests.fs b/test/FsAutoComplete.Tests.Lsp/TestExplorerTests.fs new file mode 100644 index 000000000..0a518001e --- /dev/null +++ b/test/FsAutoComplete.Tests.Lsp/TestExplorerTests.fs @@ -0,0 +1,339 @@ +module FsAutoComplete.Tests.TestExplorer + +open Expecto +open Helpers +open System.IO +open FsAutoComplete.LspHelpers +open System.Threading +open Helpers.Expecto.ShadowedTimeouts +open FsAutoComplete.Tests.Lsp.Helpers + +module TestRunResult = + open Ionide.LanguageServerProtocol.JsonRpc + + let tryUnwrapTestRunResult (res: LspResult) = + match res with + | Ok plainNotification -> + plainNotification + |> Option.get + |> _.Content + |> FsAutoComplete.JsonSerializer.readJson< + FsAutoComplete.CommandResponse.ResponseMsg + > + |> _.Data + | Error err -> failwith $"TestRunTests returned error: {err.Message}" + +module TestDiscoveryResult = + open Ionide.LanguageServerProtocol.JsonRpc + + let tryUnwrapTestDiscoveryResult (res: LspResult) = + match res with + | Ok plainNotification -> + plainNotification + |> Option.get + |> _.Content + |> FsAutoComplete.JsonSerializer.readJson< + FsAutoComplete.CommandResponse.ResponseMsg + > + |> _.Data + | Error err -> failwith $"TestDiscoverTests returned error: {err.Message}" + +module ExpectedTests = + let VSTestXUnitRunResults = + [ "Tests.My test", FsAutoComplete.TestServer.TestOutcome.Passed + "Tests.Fails", FsAutoComplete.TestServer.TestOutcome.Failed + "Tests.Skipped", FsAutoComplete.TestServer.TestOutcome.Skipped + "Tests.Exception", FsAutoComplete.TestServer.TestOutcome.Failed + "Tests+Nested.Test 1", FsAutoComplete.TestServer.TestOutcome.Passed + "Tests+Nested.Test 2", FsAutoComplete.TestServer.TestOutcome.Passed + "Tests.Expects environment variable", FsAutoComplete.TestServer.TestOutcome.Failed ] + + let VSTestXunitTests = + [ "Tests.My test", FsAutoComplete.TestServer.TestOutcome.Passed ] + +module Workspace = + + let build workspaceRoot = + let dir = DirectoryInfo workspaceRoot + + if not dir.Exists then + failwith $"Target workspace doesn't exist: {workspaceRoot}" + + let projects = dir.GetFiles("*.?sproj", SearchOption.AllDirectories) + + for project in projects do + let buildResult = DotnetCli.build project.FullName + + Expect.equal + 0 + buildResult.ExitCode + $"Workspace build failed with: {buildResult.StdErr} \nProject: {project.FullName}" + +let tests createServer = + let initializeServer workspaceRoot = + async { + let! (server, event) = serverInitialize workspaceRoot defaultConfigDto createServer + do! waitForWorkspaceFinishedParsing event + + return (server, event) + } + |> Async.Cache + + testSequenced + <| testList + "TestExplorerTests" + [ testList + "DiscoverTests" + [ testCaseAsync "it should error if the workspace hasn't been built" + <| async { + let workspaceRoot = + Path.Combine(__SOURCE_DIRECTORY__, "SampleTestProjects", "VSTest.XUnit.RunResults") + + let! server, _ = initializeServer workspaceRoot + use server = server + + let! res = server.TestDiscoverTests() + + Expect.isError res "" + } + testCaseAsync "it should discover tests in all projects" + <| async { + let workspaceRoot = Path.Combine(__SOURCE_DIRECTORY__, "SampleTestProjects") + + let! server, _ = initializeServer workspaceRoot + use server = server + + Workspace.build workspaceRoot + + let! res = server.TestDiscoverTests() + + let expected = + [ ExpectedTests.VSTestXUnitRunResults; ExpectedTests.VSTestXunitTests ] + |> List.concat + |> List.map (fun (testName, _) -> testName) + + let actual = + res |> TestDiscoveryResult.tryUnwrapTestDiscoveryResult |> List.map _.FullName + + Expect.equal (set actual) (set expected) "" + } ] + testList + "RunTests" + [ testCaseAsync "it should report tests of all basic outcomes" + <| async { + let workspaceRoot = + Path.Combine(__SOURCE_DIRECTORY__, "SampleTestProjects", "VSTest.XUnit.RunResults") + + let! server, _ = initializeServer workspaceRoot + use server = server + + let buildResult = DotnetCli.build workspaceRoot + Expect.equal 0 buildResult.ExitCode $"Build failed with: {buildResult.StdErr}" + + let runRequest: TestRunRequest = + { LimitToProjects = None + TestCaseFilter = None + AttachDebugger = false } + + let! res = server.TestRunTests(runRequest) + + let actual = + TestRunResult.tryUnwrapTestRunResult res + |> List.map (fun tr -> tr.TestItem.FullName, tr.Outcome) + + let expected = ExpectedTests.VSTestXUnitRunResults + + Expect.equal (set actual) (set expected) "" + } + + testCaseAsync "it should report a processId when debugging a test project" + <| async { + let workspaceRoot = + Path.Combine(__SOURCE_DIRECTORY__, "SampleTestProjects", "VSTest.XUnit.RunResults") + + let! server, clientNotifications = initializeServer workspaceRoot + + use server = server + + let buildResult = DotnetCli.build workspaceRoot + Expect.equal 0 buildResult.ExitCode $"Build failed with: {buildResult.StdErr}" + + use tokenSource = new CancellationTokenSource() + let mutable processIdSpy: int option = None + use! _onCancel = Async.OnCancel(fun () -> tokenSource.Cancel()) + + use _ = + clientNotifications.Subscribe(fun (msgType: string, data: obj) -> + if msgType = "test/processWaitingForDebugger" then + let processId: int = + data :?> PlainNotification + |> _.Content + |> FsAutoComplete.JsonSerializer.readJson + + processIdSpy <- Some processId + tokenSource.Cancel()) + + Expect.throwsT + (fun () -> + let runRequest: TestRunRequest = + { LimitToProjects = None + TestCaseFilter = None + AttachDebugger = true } + + Async.RunSynchronously( + server.TestRunTests(runRequest) |> Async.Ignore, + cancellationToken = tokenSource.Token + )) + "" + + Expect.isSome processIdSpy "" + + let maybeHangingTestProcess = + System.Diagnostics.Process.GetProcesses() + |> Array.tryFind (fun p -> Some p.Id = processIdSpy) + + Expect.isNone maybeHangingTestProcess "All test processes should be canceled with the test run" + } + + testCaseAsync + "it should inherit environment variables from it's parent, allowing tests to depend on environment variables" + <| async { + let workspaceRoot = + Path.Combine(__SOURCE_DIRECTORY__, "SampleTestProjects", "VSTest.XUnit.RunResults") + + let! server, _ = initializeServer workspaceRoot + + use server = server + + let buildResult = DotnetCli.build workspaceRoot + Expect.equal 0 buildResult.ExitCode $"Build failed with: {buildResult.StdErr}" + + System.Environment.SetEnvironmentVariable("dd586685-08f6-410c-a9f1-84530af117ab", "Set me") + + let! response = + server.TestRunTests( + { LimitToProjects = None + TestCaseFilter = Some "FullyQualifiedName~Tests.Expects environment variable" + AttachDebugger = false } + ) + + let expected = + [ "Tests.Expects environment variable", FsAutoComplete.TestServer.TestOutcome.Passed ] + + let actual = + TestRunResult.tryUnwrapTestRunResult response + |> List.map (fun tr -> tr.TestItem.FullName, tr.Outcome) + + Expect.equal (set actual) (set expected) "" + + System.Environment.SetEnvironmentVariable("dd586685-08f6-410c-a9f1-84530af117ab", "") + } + + testCaseAsync "it should ignore test project filters that aren't projects in the workspace" + <| async { + let workspaceRoot = + Path.Combine(__SOURCE_DIRECTORY__, "SampleTestProjects", "VSTest.XUnit.RunResults") + + let! server, _ = initializeServer workspaceRoot + + use server = server + + let buildResult = DotnetCli.build workspaceRoot + Expect.equal 0 buildResult.ExitCode $"Build failed with: {buildResult.StdErr}" + + let! response = + server.TestRunTests( + { LimitToProjects = + Some [ Path.Combine(__SOURCE_DIRECTORY__, "SampleTestProjects", "Nope", "Nope.fsproj") ] + TestCaseFilter = None + AttachDebugger = false } + ) + + let expected = [] + + let actual = + TestRunResult.tryUnwrapTestRunResult response + |> List.map (fun tr -> tr.TestItem.FullName, tr.Outcome) + + Expect.equal (set actual) (set expected) "" + } + + testCaseAsync "it should run only test projects in the project filter when specified" + <| async { + let workspaceRoot = Path.Combine(__SOURCE_DIRECTORY__, "SampleTestProjects") + + let! server, _ = initializeServer workspaceRoot + + use server = server + + Workspace.build workspaceRoot + + let! response = + server.TestRunTests( + { LimitToProjects = + Some + [ Path.Combine( + __SOURCE_DIRECTORY__, + "SampleTestProjects", + "VSTest.XUnit.RunResults", + "VSTest.XUnit.RunResults.fsproj" + ) ] + TestCaseFilter = None + AttachDebugger = false } + ) + + let expected = ExpectedTests.VSTestXUnitRunResults + + let actual = + TestRunResult.tryUnwrapTestRunResult response + |> List.map (fun tr -> tr.TestItem.FullName, tr.Outcome) + + Expect.equal (set actual) (set expected) "" + } + testCaseAsync "it should only attach the debugger for projects in the project filter if filter is specified" + <| async { + let workspaceRoot = Path.Combine(__SOURCE_DIRECTORY__, "SampleTestProjects") + + let! server, clientNotifications = initializeServer workspaceRoot + + use server = server + + Workspace.build workspaceRoot + + use tokenSource = new CancellationTokenSource() + let mutable processIdSpy: int list = [] + use! _onCancel = Async.OnCancel(fun () -> tokenSource.Cancel()) + + use _ = + clientNotifications.Subscribe(fun (msgType: string, data: obj) -> + if msgType = "test/processWaitingForDebugger" then + let processId: int = + data :?> PlainNotification + |> _.Content + |> FsAutoComplete.JsonSerializer.readJson + + processIdSpy <- processId :: processIdSpy + tokenSource.Cancel()) + + Expect.throwsT + (fun () -> + let runRequest: TestRunRequest = + { LimitToProjects = + Some + [ Path.Combine( + __SOURCE_DIRECTORY__, + "SampleTestProjects", + "VSTest.XUnit.RunResults", + "VSTest.XUnit.RunResults.fsproj" + ) ] + TestCaseFilter = None + AttachDebugger = true } + + Async.RunSynchronously( + server.TestRunTests(runRequest) |> Async.Ignore, + cancellationToken = tokenSource.Token + )) + "" + + Expect.hasLength processIdSpy 1 "Should only launch one process to debug" + } ] ] diff --git a/test/FsAutoComplete.Tests.Lsp/Utils/Server.fs b/test/FsAutoComplete.Tests.Lsp/Utils/Server.fs index aee896153..3705aed69 100644 --- a/test/FsAutoComplete.Tests.Lsp/Utils/Server.fs +++ b/test/FsAutoComplete.Tests.Lsp/Utils/Server.fs @@ -322,7 +322,7 @@ module Document = do! doc.Server.Server.TextDocumentDidOpen p try - return! doc |> waitForLatestDiagnostics Helpers.defaultTimeout + return! doc |> waitForLatestDiagnostics (TimeSpan.FromSeconds(2.)) with :? TimeoutException -> return failwith $"Timeout waiting for latest diagnostics for {doc.Uri}" } diff --git a/test/FsAutoComplete.Tests.TestExplorer/FsAutoComplete.Tests.TestExplorer.fsproj b/test/FsAutoComplete.Tests.TestExplorer/FsAutoComplete.Tests.TestExplorer.fsproj new file mode 100644 index 000000000..7e80883d0 --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/FsAutoComplete.Tests.TestExplorer.fsproj @@ -0,0 +1,24 @@ + + + Exe + net8.0 + false + + + + + + + + + + + + + dotnet build $(MSBuildProjectDirectory)/SampleTestProjects/VSTest.XUnit.Tests/ + dotnet build $(MSBuildProjectDirectory)/SampleTestProjects/VSTest.XUnit.RunResults/ + dotnet build $(MSBuildProjectDirectory)/SampleTestProjects/VSTest.NUnit/ + + + + \ No newline at end of file diff --git a/test/FsAutoComplete.Tests.TestExplorer/Main.fs b/test/FsAutoComplete.Tests.TestExplorer/Main.fs new file mode 100644 index 000000000..43147fdfd --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/Main.fs @@ -0,0 +1,7 @@ +namespace FsAutoComplete.Tests.TestExplorer + +open Expecto + +module Program = + [] + let main argv = Tests.runTestsInAssemblyWithCLIArgs [] argv diff --git a/test/FsAutoComplete.Tests.TestExplorer/ResourceLocators.fs b/test/FsAutoComplete.Tests.TestExplorer/ResourceLocators.fs new file mode 100644 index 000000000..8d0cbbcbb --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/ResourceLocators.fs @@ -0,0 +1,18 @@ +module internal ResourceLocators + +open FsAutoComplete.TestServer +open System.IO + +let tryFindVsTest () : string = + let dotnetBinary = + Ionide.ProjInfo.Paths.dotnetRoot.Value + |> Option.defaultWith (fun () -> + failwith "Couldn't find dotnet root. The dotnet sdk must be installed to run these tests") + + let cwd = System.Environment.CurrentDirectory |> Some + + VSTestWrapper.tryFindVsTestFromDotnetRoot dotnetBinary.FullName cwd + |> Result.defaultWith failwith + |> _.FullName + +let sampleProjectsRootDir = Path.Combine(__SOURCE_DIRECTORY__, "SampleTestProjects") diff --git a/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.NUnit/UnitTest1.fs b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.NUnit/UnitTest1.fs new file mode 100644 index 000000000..69949b6d0 --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.NUnit/UnitTest1.fs @@ -0,0 +1,12 @@ +module VSTest.NUnit + +open NUnit.Framework + +[] +let Setup () = () + +[] +let Test1 () = Assert.Pass() + +[] +let Test2 () = Assert.Pass() diff --git a/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.NUnit/VSTest.NUnit.fsproj b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.NUnit/VSTest.NUnit.fsproj new file mode 100644 index 000000000..a6bee0e16 --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.NUnit/VSTest.NUnit.fsproj @@ -0,0 +1,24 @@ + + + + net8.0 + latest + false + true + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.RunResults/Program.fs b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.RunResults/Program.fs new file mode 100644 index 000000000..31dc4f735 --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.RunResults/Program.fs @@ -0,0 +1,4 @@ +module Program + +[] +let main _ = 0 diff --git a/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.RunResults/Tests.fs b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.RunResults/Tests.fs new file mode 100644 index 000000000..28e514dbe --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.RunResults/Tests.fs @@ -0,0 +1,30 @@ +module Tests + +open System +open Xunit + +[] +let ``My test`` () = + System.Console.WriteLine("Where do I show up in the results") + Assert.True(true) + +[] +let ``Fails`` () = Assert.True(false) + +[] +let ``Skipped`` () = Assert.True(true) + +[] +let ``Exception`` () : unit = failwith "Report as an exception" + +[] +let ``Expects environment variable`` () : unit = + Assert.Equal("Set me", Environment.GetEnvironmentVariable("dd586685-08f6-410c-a9f1-84530af117ab")) + + +module Nested = + [] + let ``Test 1`` () : unit = () + + [] + let ``Test 2`` () : unit = () diff --git a/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.RunResults/VSTest.XUnit.RunResults.fsproj b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.RunResults/VSTest.XUnit.RunResults.fsproj new file mode 100644 index 000000000..1a4224efd --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.RunResults/VSTest.XUnit.RunResults.fsproj @@ -0,0 +1,22 @@ + + + + net8.0 + false + false + + + + + + + + + + + + + + + + diff --git a/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.Tests/Program.fs b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.Tests/Program.fs new file mode 100644 index 000000000..31dc4f735 --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.Tests/Program.fs @@ -0,0 +1,4 @@ +module Program + +[] +let main _ = 0 diff --git a/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.Tests/Tests.fs b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.Tests/Tests.fs new file mode 100644 index 000000000..7f70fae19 --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.Tests/Tests.fs @@ -0,0 +1,8 @@ +module Tests + +open System +open Xunit + +[] +let ``My test`` () = + Assert.True(true) diff --git a/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.Tests/VSTest.XUnit.Tests.fsproj b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.Tests/VSTest.XUnit.Tests.fsproj new file mode 100644 index 000000000..1a4224efd --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/VSTest.XUnit.Tests/VSTest.XUnit.Tests.fsproj @@ -0,0 +1,22 @@ + + + + net8.0 + false + false + + + + + + + + + + + + + + + + diff --git a/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/paket.dependencies b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/paket.dependencies new file mode 100644 index 000000000..07e4b673d --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/paket.dependencies @@ -0,0 +1,2 @@ +# SampleTestProjects aren't managed with paket +# This is here to prevent the FsAutoComplete paket from trying manage sample project dependencies \ No newline at end of file diff --git a/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/paket.lock b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/paket.lock new file mode 100644 index 000000000..d3f5a12fa --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/SampleTestProjects/paket.lock @@ -0,0 +1 @@ + diff --git a/test/FsAutoComplete.Tests.TestExplorer/TestDiscoveryTests.fs b/test/FsAutoComplete.Tests.TestExplorer/TestDiscoveryTests.fs new file mode 100644 index 000000000..f5cfbc326 --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/TestDiscoveryTests.fs @@ -0,0 +1,33 @@ +module TestDiscoveryTests + +open Expecto +open FsAutoComplete.TestServer +open Microsoft.VisualStudio.TestPlatform.ObjectModel; +open System.IO + + +let vstestPath = ResourceLocators.tryFindVsTest () + +[] +let tests = + testList "VSTestWrapper Test Discovery" [ + testCaseAsync "should return an empty list if given no projects" <| async { + let expected = [] + let! actual = VSTestWrapper.discoverTestsAsync vstestPath ignore [] + Expect.equal actual expected "" + } + + testCaseAsync "should discover tests given a single xunit project" <| async { + let expectedTestIds = ["Tests.My test"] + + let sources = [ + Path.Combine(ResourceLocators.sampleProjectsRootDir, "VSTest.XUnit.Tests/bin/Debug/net8.0/VSTest.XUnit.Tests.dll") + ] + + let! discovered = VSTestWrapper.discoverTestsAsync vstestPath ignore sources + let actual = discovered |> List.map _.FullyQualifiedName + + Expect.equal actual expectedTestIds "" + } + + ] diff --git a/test/FsAutoComplete.Tests.TestExplorer/TestRunTests.fs b/test/FsAutoComplete.Tests.TestExplorer/TestRunTests.fs new file mode 100644 index 000000000..0b4f69113 --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/TestRunTests.fs @@ -0,0 +1,159 @@ +module TestRunTests + +open Expecto +open FsAutoComplete.TestServer +open Microsoft.VisualStudio.TestPlatform.ObjectModel +open System.IO + +let vstestPath = ResourceLocators.tryFindVsTest () + +let nullAttachDebugger _ = false + +[] +let tests = + testList + "VSTestWrapper Test Run" + [ testCaseAsync "it should return an empty list if given no projects" + <| async { + let expected = [] + let! actual = VSTestWrapper.runTestsAsync vstestPath ignore nullAttachDebugger [] None false + Expect.equal actual expected "" + } + + testCaseAsync "it should be able to report basic test run outcomes" + <| async { + let expected = + [ "Tests.My test", TestOutcome.Passed + "Tests.Fails", TestOutcome.Failed + "Tests.Skipped", TestOutcome.Skipped + "Tests.Exception", TestOutcome.Failed + "Tests+Nested.Test 1", TestOutcome.Passed + "Tests+Nested.Test 2", TestOutcome.Passed + "Tests.Expects environment variable", TestOutcome.Failed ] + + let sources = + [ Path.Combine( + ResourceLocators.sampleProjectsRootDir, + "VSTest.XUnit.RunResults/bin/Debug/net8.0/VSTest.XUnit.RunResults.dll" + ) ] + + let! runResults = VSTestWrapper.runTestsAsync vstestPath ignore nullAttachDebugger sources None false + + let likenessOfTestResult (result: TestResult) = (result.TestCase.FullyQualifiedName, result.Outcome) + let actual = runResults |> List.map likenessOfTestResult + + Expect.equal (set actual) (set expected) "" + } + + testCaseAsync "it should run only tests that match the case filter" + <| async { + let expected = + [ ("Tests+Nested.Test 1", TestOutcome.Passed) + ("Tests+Nested.Test 2", TestOutcome.Passed) ] + + let sources = + [ Path.Combine( + ResourceLocators.sampleProjectsRootDir, + "VSTest.XUnit.RunResults/bin/Debug/net8.0/VSTest.XUnit.RunResults.dll" + ) ] + + let! runResults = + VSTestWrapper.runTestsAsync + vstestPath + ignore + nullAttachDebugger + sources + (Some "FullyQualifiedName~Tests+Nested") + false + + let likenessOfTestResult (result: TestResult) = (result.TestCase.FullyQualifiedName, result.Outcome) + let actual = runResults |> List.map likenessOfTestResult + + Expect.equal (set actual) (set expected) "" + } + + testCaseAsync "it should respect test filters on NUnit projects" + <| async { + // NOTE: This is an NUnit bug. NUnit doesn't respect filters when VSTest is in Design Mode, which VsTestConsoleWrapper is by default + // https://github.com/ionide/FsAutoComplete/pull/1383#issuecomment-3245590606 + let expected = [ "VSTest.NUnit.Test1", TestOutcome.Passed ] + + let sources = + [ Path.Combine(ResourceLocators.sampleProjectsRootDir, "VSTest.NUnit/bin/Debug/net8.0/VSTest.NUnit.dll") ] + + let! runResults = + VSTestWrapper.runTestsAsync + vstestPath + ignore + nullAttachDebugger + sources + (Some "FullyQualifiedName~Test1") + false + + let likenessOfTestResult (result: TestResult) = (result.TestCase.FullyQualifiedName, result.Outcome) + let actual = runResults |> List.map likenessOfTestResult + + Expect.equal (set actual) (set expected) "" + } + + testCaseAsync "it should report processIds when debugging is on" + <| async { + use tokenSource = new System.Threading.CancellationTokenSource(2000) + + let mutable actualProcessId: int option = None + + let updateSpy (processId: int) = + actualProcessId <- Some processId + tokenSource.Cancel() + false + + use! _c = Async.OnCancel(fun _ -> tokenSource.Cancel()) + + Expect.throwsT + (fun () -> + let sources = + [ Path.Combine( + ResourceLocators.sampleProjectsRootDir, + "VSTest.XUnit.RunResults/bin/Debug/net8.0/VSTest.XUnit.RunResults.dll" + ) ] + + Async.RunSynchronously( + VSTestWrapper.runTestsAsync vstestPath ignore updateSpy sources None true, + cancellationToken = tokenSource.Token + ) + |> ignore) + "" + + Expect.isSome actualProcessId "Expected runTest to report a processId" + } + + testCaseAsync "it should report a processId only once per process" + <| async { + use tokenSource = new System.Threading.CancellationTokenSource(1000) + + let mutable reportedProcessIds: int list = [] + + let updateSpy (processId: int) = + reportedProcessIds <- processId :: reportedProcessIds + tokenSource.Cancel() + false + + use! _c = Async.OnCancel(fun _ -> tokenSource.Cancel()) + + Expect.throwsT + (fun () -> + let sources = + [ Path.Combine( + ResourceLocators.sampleProjectsRootDir, + "VSTest.XUnit.RunResults/bin/Debug/net8.0/VSTest.XUnit.RunResults.dll" + ) ] + + Async.RunSynchronously( + VSTestWrapper.runTestsAsync vstestPath ignore updateSpy sources None true, + cancellationToken = tokenSource.Token + ) + |> ignore) + "" + + Expect.hasLength reportedProcessIds 1 "Expected runTest to report a processId" + } ] diff --git a/test/FsAutoComplete.Tests.TestExplorer/paket.references b/test/FsAutoComplete.Tests.TestExplorer/paket.references new file mode 100644 index 000000000..dfc4a00cb --- /dev/null +++ b/test/FsAutoComplete.Tests.TestExplorer/paket.references @@ -0,0 +1,4 @@ +FSharp.Core content: once +Microsoft.NET.Test.Sdk +YoloDev.Expecto.TestSdk +Microsoft.Bcl.AsyncInterfaces \ No newline at end of file