diff --git a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj index 7bc4f6e058f..d899b551e30 100644 --- a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj +++ b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj @@ -102,7 +102,8 @@ - $(NoWarn);NU1510 + $(NoWarn);NU1510;44 + diff --git a/tests/FSharp.Test.Utilities/VSInstallDiscovery.fs b/tests/FSharp.Test.Utilities/VSInstallDiscovery.fs index e491ea329bd..e59eec8eed2 100644 --- a/tests/FSharp.Test.Utilities/VSInstallDiscovery.fs +++ b/tests/FSharp.Test.Utilities/VSInstallDiscovery.fs @@ -126,3 +126,55 @@ module VSInstallDiscovery = | NotFound reason -> logAction $"Visual Studio installation not found: {reason}" None + + /// Gets the VS installation directory or fails with a detailed error message. + /// This is the recommended method for test scenarios that require VS to be installed. + let getVSInstallDirOrFail () : string = + match tryFindVSInstallation () with + | Found (path, _) -> path + | NotFound reason -> + failwith $"Visual Studio installation not found: {reason}. Ensure VS is installed or environment variables (VSAPPIDDIR, VS*COMNTOOLS) are set." + +/// Assembly resolver for Visual Studio test infrastructure. +/// Provides centralized assembly resolution for VS integration tests. +module VSAssemblyResolver = + open System + open System.IO + open System.Reflection + open System.Globalization + + /// Adds an assembly resolver that probes Visual Studio installation directories. + /// This should be called early in test initialization to ensure VS assemblies can be loaded. + let addResolver () = + let vsInstallDir = VSInstallDiscovery.getVSInstallDirOrFail () + + let probingPaths = + [| + Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\Editor") + Path.Combine(vsInstallDir, @"IDE\PublicAssemblies") + Path.Combine(vsInstallDir, @"IDE\PrivateAssemblies") + Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\ManagedLanguages\VBCSharp\LanguageServices") + Path.Combine(vsInstallDir, @"IDE\Extensions\Microsoft\CodeSense\Framework") + Path.Combine(vsInstallDir, @"IDE") + |] + + AppDomain.CurrentDomain.add_AssemblyResolve(fun _ args -> + let found () = + probingPaths + |> Seq.tryPick (fun p -> + try + let name = AssemblyName(args.Name) + let codebase = Path.GetFullPath(Path.Combine(p, name.Name) + ".dll") + if File.Exists(codebase) then + name.CodeBase <- codebase + name.CultureInfo <- Unchecked.defaultof + name.Version <- Unchecked.defaultof + Some name + else + None + with _ -> + None) + + match found () with + | None -> Unchecked.defaultof + | Some name -> Assembly.Load(name)) diff --git a/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs b/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs index 78a57793517..ef8c01ca908 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/Helpers/AssemblyResolver.fs @@ -2,62 +2,9 @@ namespace FSharp.Editor.Tests.Helpers -open System -open System.IO -open System.Reflection - module AssemblyResolver = - open System.Globalization - open FSharp.Test.VSInstallDiscovery - - let vsInstallDir = - // Use centralized VS installation discovery with graceful fallback - match tryGetVSInstallDir () with - | Some dir -> dir - | None -> - // Fallback to legacy behavior for backward compatibility - let vsvar = - let var = Environment.GetEnvironmentVariable("VS170COMNTOOLS") - - if String.IsNullOrEmpty var then - Environment.GetEnvironmentVariable("VSAPPIDDIR") - else - var - - if String.IsNullOrEmpty vsvar then - failwith "VS170COMNTOOLS and VSAPPIDDIR environment variables not found." - - Path.Combine(vsvar, "..") - - let probingPaths = - [| - Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\Editor") - Path.Combine(vsInstallDir, @"IDE\PublicAssemblies") - Path.Combine(vsInstallDir, @"IDE\PrivateAssemblies") - Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\ManagedLanguages\VBCSharp\LanguageServices") - Path.Combine(vsInstallDir, @"IDE\Extensions\Microsoft\CodeSense\Framework") - Path.Combine(vsInstallDir, @"IDE") - |] - - let addResolver () = - AppDomain.CurrentDomain.add_AssemblyResolve (fun h args -> - let found () = - (probingPaths) - |> Seq.tryPick (fun p -> - try - let name = AssemblyName(args.Name) - let codebase = Path.GetFullPath(Path.Combine(p, name.Name) + ".dll") - - if File.Exists(codebase) then - name.CodeBase <- codebase - name.CultureInfo <- Unchecked.defaultof - name.Version <- Unchecked.defaultof - Some(name) - else - None - with _ -> - None) + open FSharp.Test.VSAssemblyResolver - match found () with - | None -> Unchecked.defaultof - | Some name -> Assembly.Load(name)) + /// Adds an assembly resolver that probes Visual Studio installation directories. + /// This is a compatibility shim that delegates to the centralized implementation. + let addResolver = addResolver diff --git a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj index f7410214430..d41b116c21e 100644 --- a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj +++ b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj @@ -24,6 +24,9 @@ UnitTests.TestLib.Utils.fs + + VSInstallDiscovery.fs + diff --git a/vsintegration/tests/Salsa/VsMocks.fs b/vsintegration/tests/Salsa/VsMocks.fs index 1ce67c04763..54fe8b906de 100644 --- a/vsintegration/tests/Salsa/VsMocks.fs +++ b/vsintegration/tests/Salsa/VsMocks.fs @@ -1642,6 +1642,7 @@ module internal VsActual = open System.ComponentModel.Composition.Primitives open Microsoft.VisualStudio.Text open Microsoft.VisualStudio.Threading + open FSharp.Test.VSInstallDiscovery type TestExportJoinableTaskContext () = @@ -1650,22 +1651,7 @@ module internal VsActual = [)>] member public _.JoinableTaskContext : JoinableTaskContext = jtc - let vsInstallDir = - // use the environment variable to find the VS installdir - let vsvar = - // Try VS180COMNTOOLS first, then VS170COMNTOOLS, then VSAPPIDDIR - // TODO : use tryGetVSInstallDir from test utils instead - let var18 = Environment.GetEnvironmentVariable("VS180COMNTOOLS") - if String.IsNullOrEmpty var18 then - let var17 = Environment.GetEnvironmentVariable("VS170COMNTOOLS") - if String.IsNullOrEmpty var17 then - Environment.GetEnvironmentVariable("VSAPPIDDIR") - else - var17 - else - var18 - if String.IsNullOrEmpty vsvar then failwith "VS180COMNTOOLS, VS170COMNTOOLS and VSAPPIDDIR environment variables not found." - Path.Combine(vsvar, "..") + let vsInstallDir = getVSInstallDirOrFail () let CreateEditorCatalog() = let thisAssembly = Assembly.GetExecutingAssembly().Location diff --git a/vsintegration/tests/UnitTests/AssemblyResolver.fs b/vsintegration/tests/UnitTests/AssemblyResolver.fs index 38b5ee45290..cf36b723e40 100644 --- a/vsintegration/tests/UnitTests/AssemblyResolver.fs +++ b/vsintegration/tests/UnitTests/AssemblyResolver.fs @@ -1,52 +1,8 @@ namespace Microsoft.VisualStudio.FSharp -open System -open System.IO -open System.Reflection - module AssemblyResolver = - open System.Globalization - open FSharp.Test.VSInstallDiscovery - - let vsInstallDir = - // Use centralized VS installation discovery with graceful fallback - match tryGetVSInstallDir () with - | Some dir -> dir - | None -> - // Fallback to legacy behavior for backward compatibility - let vsvar = - let var = Environment.GetEnvironmentVariable("VS170COMNTOOLS") - if String.IsNullOrEmpty var then - Environment.GetEnvironmentVariable("VSAPPIDDIR") - else - var - if String.IsNullOrEmpty vsvar then failwith "VS170COMNTOOLS and VSAPPIDDIR environment variables not found." - Path.Combine(vsvar, "..") - - let probingPaths = [| - Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\Editor") - Path.Combine(vsInstallDir, @"IDE\PublicAssemblies") - Path.Combine(vsInstallDir, @"IDE\PrivateAssemblies") - Path.Combine(vsInstallDir, @"IDE\CommonExtensions\Microsoft\ManagedLanguages\VBCSharp\LanguageServices") - Path.Combine(vsInstallDir, @"IDE\Extensions\Microsoft\CodeSense\Framework") - Path.Combine(vsInstallDir, @"IDE") - |] + open FSharp.Test.VSAssemblyResolver - let addResolver () = - AppDomain.CurrentDomain.add_AssemblyResolve(fun h args -> - let found () = - (probingPaths ) |> Seq.tryPick(fun p -> - try - let name = AssemblyName(args.Name) - let codebase = Path.GetFullPath(Path.Combine(p, name.Name) + ".dll") - if File.Exists(codebase) then - name.CodeBase <- codebase - name.CultureInfo <- Unchecked.defaultof - name.Version <- Unchecked.defaultof - Some (name) - else None - with | _ -> None - ) - match found() with - | None -> Unchecked.defaultof - | Some name -> Assembly.Load(name) ) + /// Adds an assembly resolver that probes Visual Studio installation directories. + /// This is a compatibility shim that delegates to the centralized implementation. + let addResolver = addResolver