// Copyright (c) Microsoft Corporation.  All Rights Reserved.  See License.txt in the project root for license information.

namespace Tests.LanguageService.Script

open System
open System.IO
open System.Reflection
open Xunit
open Salsa.Salsa
open Salsa.VsOpsUtils
open UnitTests.TestLib.Salsa
open UnitTests.TestLib.Utils
open UnitTests.TestLib.LanguageService
open UnitTests.TestLib.ProjectSystem

type UsingMSBuild() as this = 
    inherit LanguageServiceBaseTests() 

    let notAA l = None,l

    let createSingleFileFsx (code : string) = 
        let (_, p, f) = this.CreateSingleFileProject(code, fileKind = SourceFileKind.FSX)
        (p, f)

    let createSingleFileFsxFromLines (code : string list) = 
        let (_, p, f) = this.CreateSingleFileProject(code, fileKind = SourceFileKind.FSX)
        (p, f)

    (* Timings ----------------------------------------------------------------------------- *)
    let stopWatch = new System.Diagnostics.Stopwatch()
    let ResetStopWatch() = stopWatch.Reset(); stopWatch.Start()
    let time1 op a message = 
        ResetStopWatch()
        let result = op a
        printf "%s %d ms\n" message stopWatch.ElapsedMilliseconds
        result

    let ShowErrors(project:OpenProject) =     
        for error in (GetErrors(project)) do
            printf "%s\n" (error.ToString())  

    let AssertListContainsInOrder(s:string list,cs:string list) =
        let s : string array = Array.ofList s
        let s : string = String.Join("\n",s)
        AssertContainsInOrder(s,cs)

    /// Assert that there is no squiggle.
    let AssertNoSquiggle(squiggleOption) = 
        match squiggleOption with 
        | None -> ()
        | Some(severity,message) ->
            Assert.Fail(sprintf "Expected no squiggle but got '%A' with message: %s" severity message)

    let VerifyErrorListContainedExpectedStr(expectedStr:string,project : OpenProject) = 
        let errorList = GetErrors(project)
        let GetErrorMessages(errorList : Error list) =
            [ for i = 0 to errorList.Length - 1 do
                yield errorList.[i].Message]
            
        Assert.True(errorList
                          |> GetErrorMessages
                          |> Seq.exists (fun errorMessage ->
                                errorMessage.Contains(expectedStr)))

    let AssertNoErrorsOrWarnings(project:OpenProject) = 
        let count = List.length (GetErrors(project))
        if count<>0 then
            printf "Saw %d errors and expected none.\n" count
            printf "Errors are: \n" 
            for e in GetErrors project do 
                printf "  path = <<<%s>>>\n" e.Path
                printf "  message = <<<%s> \n" e.Message 
            AssertEqual(0,count)

    let AssertExactlyCountErrorSeenContaining(project:OpenProject,text,expectedCount) =
        let nMatching = (GetErrors(project)) |> List.filter (fun e ->e.ToString().Contains(text)) |> List.length
        match nMatching with
        | 0 -> 
            failwith (sprintf "No errors containing \"%s\"" text)
        | x when x = expectedCount -> ()
        | _ -> 
            failwith (sprintf "Multiple errors containing \"%s\"" text)

    let AssertExactlyOneErrorSeenContaining(project:OpenProject,text) =
        AssertExactlyCountErrorSeenContaining(project,text,1)

    /// Assert that a given squiggle is an Error (or warning) containing the given text        
    let AssertSquiggleIsErrorContaining,AssertSquiggleIsWarningContaining, AssertSquiggleIsErrorNotContaining,AssertSquiggleIsWarningNotContaining =         
        let AssertSquiggle expectedSeverity nameOfExpected nameOfNotExpected assertf (squiggleOption,containing) = 
            match squiggleOption with
            | None -> Assert.Fail("Expected a squiggle but none was seen.")
            | Some(severity,message) ->
                Assert.True((severity=expectedSeverity), sprintf "Expected %s but saw %s: %s" nameOfExpected nameOfNotExpected message)
                assertf(message,containing)        
        AssertSquiggle Microsoft.VisualStudio.FSharp.LanguageService.Severity.Error    "Error"    "Warning" AssertContains,
        AssertSquiggle Microsoft.VisualStudio.FSharp.LanguageService.Severity.Warning  "Warning"  "Error"   AssertContains,
        AssertSquiggle Microsoft.VisualStudio.FSharp.LanguageService.Severity.Error    "Error"    "Warning" AssertNotContains,
        AssertSquiggle Microsoft.VisualStudio.FSharp.LanguageService.Severity.Warning  "Warning"  "Error"   AssertNotContains 


    //Verify the error list in fsx file contained the expected string
    member private this.VerifyFSXErrorListContainedExpectedString(fileContents : string, expectedStr : string) =
        let (_, project, file) = this.CreateSingleFileProject(fileContents, fileKind = SourceFileKind.FSX)
        VerifyErrorListContainedExpectedStr(expectedStr,project)    

    //Verify no error list in fsx file 
    member private this.VerifyFSXNoErrorList(fileContents : string) =
        let (_, project, file) = this.CreateSingleFileProject(fileContents, fileKind = SourceFileKind.FSX)
        AssertNoErrorsOrWarnings(project)  
    //Verify QuickInfo Contained In Fsx file
    member public this.AssertQuickInfoContainsAtEndOfMarkerInFsxFile (code : string) marker expected =

        let (_, _, file) = this.CreateSingleFileProject(code, fileKind = SourceFileKind.FSX)

        MoveCursorToEndOfMarker(file, marker)
        let tooltip = GetQuickInfoAtCursor file
        AssertContains(tooltip, expected)
    //Verify QuickInfo Contained In Fsx file
    member public this.AssertQuickInfoContainsAtStartOfMarkerInFsxFile (code : string) marker expected =
        let (_, _, file) = this.CreateSingleFileProject((code : string), fileKind = SourceFileKind.FSX)

        MoveCursorToStartOfMarker(file, marker)
        let tooltip = GetQuickInfoAtCursor file
        AssertContains(tooltip, expected)
    //Verify QuickInfo Not Contained In Fsx file     
    member public this.AssertQuickInfoNotContainsAtEndOfMarkerInFsxFile code marker notexpected =
        let (_, _, file) = this.CreateSingleFileProject((code : string), fileKind = SourceFileKind.FSX)

        MoveCursorToEndOfMarker(file, marker)
        let tooltip = GetQuickInfoAtCursor file
        AssertNotContains(tooltip, notexpected)

    /// There was a problem with Salsa that caused squiggles not to be shown for .fsx files.
    [<Fact>]
    member public this.``Fsx.Squiggles.ShowInFsxFiles``() =  
        let fileContent = """open Thing1.Thing2"""
        this.VerifyFSXErrorListContainedExpectedString(fileContent,"Thing1")
        
    /// Regression test for FSharp1.0:4861 - #r to nonexistent file causes the first line to be squiggled
    /// There was a problem with Salsa that caused squiggles not to be shown for .fsx files.
    [<Fact>]
    member public this.``Fsx.Hash.RProperSquiggleForNonExistentFile``() =  
        let fileContent = """#r "NonExistent" """
        this.VerifyFSXErrorListContainedExpectedString(fileContent,"was not found or is invalid") 

    /// Nonexistent hash. There was a problem with Salsa that caused squiggles not to be shown for .fsx files.
    [<Fact>]
    member public this.``Fsx.Hash.RDoesNotExist.Bug3325``() =  
        let fileContent = """#r "ThisDLLDoesNotExist" """
        this.VerifyFSXErrorListContainedExpectedString(fileContent,"'ThisDLLDoesNotExist' was not found or is invalid") 

    // There was a spurious error message on the first line.
    [<Fact>]
    member public this.``Fsx.ExactlyOneError.Bug4861``() =  
        let code = 
                                      ["#light" // First line is important in this repro
                                       "#r \"Nonexistent\""
                                       ]
        let (project, _) = createSingleFileFsxFromLines code
        AssertExactlyCountErrorSeenContaining(project, "Nonexistent", 1)   // ...and not an error on the first line.
        
    [<Fact>]
    member public this.``Fsx.InvalidHashLoad.ShouldBeASquiggle.Bug3012``() =  
        let fileContent = """
            #light
            #load "Bar.fs"
            """
        this.VerifyFSXErrorListContainedExpectedString(fileContent,"Bar.fs") 

    // Transitive to existing property.
    [<Fact>]
    member public this.``Fsx.ScriptClosure.TransitiveLoad1``() = 
        use _guard = this.UsingNewVS() 
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public Property = 0"
                                     ])    
        let file1 = OpenFile(project,"File1.fs")   
        let script2 = AddFileFromText(project,"Script2.fsx",
                                      ["#load \"File1.fs\""
                                       ])    
        let script2 = OpenFile(project,"Script2.fsx")   
        let script2 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"Script1.fsx\""
                                       "Namespace.Foo.Property" 
                                       ])    
        let script2 = OpenFile(project,"Script2.fsx")   
        TakeCoffeeBreak(this.VS)
        AssertNoErrorsOrWarnings(project)

    // Transitive to nonexisting property.
    [<Fact>]
    member public this.``Fsx.ScriptClosure.TransitiveLoad2``() = 
        use _guard = this.UsingNewVS()  
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public Property = 0"
                                     ])    
        let script2 = AddFileFromText(project,"Script2.fsx",
                                      ["#load \"File1.fs\""
                                       ])    
        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"Script2.fsx\""
                                       "Namespace.Foo.NonExistingProperty" 
                                       ])    
        let script1 = OpenFile(project,"Script1.fsx")   
        TakeCoffeeBreak(this.VS)
        AssertExactlyOneErrorSeenContaining(project, "NonExistingProperty")

    /// FEATURE: Typing a #r into a file will cause it to be recognized by intellisense.
    [<Fact>]
    member public this.``Fsx.HashR.AddedIn``() =  
        let code =
                                    ["#light"
                                     "//#r \"System.Transactions.dll\"" // Pick anything that isn't in the standard set of assemblies.
                                     "open System.Transactions"
                                     ]
        let (project, file) = createSingleFileFsxFromLines code
        VerifyErrorListContainedExpectedStr("Transactions",project)
        
        let gpatcc = GlobalParseAndTypeCheckCounter.StartNew(this.VS)
        ReplaceFileInMemory file
                        ["#light"
                         "#r \"System.Transactions.dll\"" // <-- Uncomment this line
                         "open System.Transactions"
                         ]
        AssertNoErrorsOrWarnings(project)
        gpatcc.AssertExactly(notAA[file],notAA[file], true (* expectCreate, because dependent DLL set changed *))

    // FEATURE: Adding a #load to a file will cause types from that file to be visible in intellisense
    [<Fact>]
    member public this.``Fsx.HashLoad.Added``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fs = AddFileFromText(project,"File1.fs",
                                    ["#light"
                                     "namespace MyNamespace" 
                                     "    module MyModule ="
                                     "        let x = 1" 
                                     ])            
        
        let fsx = AddFileFromText(project,"File2.fsx",
                                    ["#light"
                                     "//#load \"File1.fs\"" 
                                     "open MyNamespace.MyModule"
                                     "printfn \"%d\" x"
                                     ])    
        let fsx = OpenFile(project,"File2.fsx")    
        VerifyErrorListContainedExpectedStr("MyNamespace",project)
        
        ReplaceFileInMemory fsx
                         ["#light"
                          "#load \"File1.fs\"" 
                          "open MyNamespace.MyModule"
                          "printfn \"%d\" x"
                          ]
        TakeCoffeeBreak(this.VS)
        AssertNoErrorsOrWarnings(project)

    // FEATURE: Removing a #load to a file will cause types from that file to no longer be visible in intellisense
    [<Fact>]
    member public this.``Fsx.HashLoad.Removed``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fs = AddFileFromText(project,"File1.fs",
                                    ["#light"
                                     "namespace MyNamespace" 
                                     "    module MyModule ="
                                     "        let x = 1" 
                                     ])            
        
        let fsx = AddFileFromText(project,"File2.fsx",
                                    ["#light"
                                     "#load \"File1.fs\"" 
                                     "open MyNamespace.MyModule"
                                     "printfn \"%d\" x"
                                     ])    
        let fsx = OpenFile(project,"File2.fsx")    
        AssertNoErrorsOrWarnings(project)
        
        ReplaceFileInMemory fsx
                         ["#light"
                          "//#load \"File1.fs\"" 
                          "open MyNamespace.MyModule"
                          "printfn \"%d\" x"
                          ]
        TakeCoffeeBreak(this.VS)
        VerifyErrorListContainedExpectedStr("MyNamespace",project)
    
    [<Fact>]
    member public this.``Fsx.HashLoad.Conditionals``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fs = AddFileFromText(project,"File1.fs",
                                    ["module InDifferentFS"
                                     "#if INTERACTIVE"
                                     "let x = 1"
                                     "#else"
                                     "let y = 2"
                                     "#endif"
                                     "#if DEBUG"
                                     "let A = 3"
                                     "#else"
                                     "let B = 4"
                                     "#endif"
                                     ])            
        
        let fsx = AddFileFromText(project,"File2.fsx",
                                    [
                                     "#load \"File1.fs\"" 
                                     "InDifferentFS."
                                     ])    
        let fsx = OpenFile(project,"File2.fsx")

        MoveCursorToEndOfMarker(fsx, "InDifferentFS.")
        let completion = AutoCompleteAtCursor fsx
        let completion = completion |> Array.map (fun (CompletionItem(name, _, _, _, _)) -> name) |> set
        Assert.Equal(Set.count completion, 2)
        Assert.True(completion.Contains "x", "Completion list should contain x because INTERACTIVE is defined")
        Assert.True(completion.Contains "B", "Completion list should contain B because DEBUG is not defined")
        

    /// FEATURE: Removing a #r into a file will cause it to no longer be seen by intellisense.
    [<Fact>]
    member public this.``Fsx.HashR.Removed``() =  
        let code =
                                    ["#light"
                                     "#r \"System.Transactions.dll\"" // Pick anything that isn't in the standard set of assemblies.
                                     "open System.Transactions"
                                     ]
        let (project, file) = createSingleFileFsxFromLines code
        TakeCoffeeBreak(this.VS)
        AssertNoErrorsOrWarnings(project)  
        
        let gpatcc = GlobalParseAndTypeCheckCounter.StartNew(this.VS)
        ReplaceFileInMemory file
                        ["#light"
                         "//#r \"System.Transactions.dll\"" // <-- Comment this line
                         "open System.Transactions"
                         ]
        SaveFileToDisk(file)
        TakeCoffeeBreak(this.VS)
        VerifyErrorListContainedExpectedStr("Transactions",project)
        gpatcc.AssertExactly(notAA[file], notAA[file], true (* expectCreate, because dependent DLL set changed *))
    


    // Corecursive load to existing property.
    [<Fact>]
    member public this.``Fsx.NoError.ScriptClosure.TransitiveLoad3``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public Property = 0"
                                     ])    
        let script2 = AddFileFromText(project,"Script2.fsx",
                                      ["#load \"Script1.fsx\""
                                       "#load \"File1.fs\""
                                       ])    
        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"Script2.fsx\""
                                       "#load \"File1.fs\""
                                       "Namespace.Foo.Property" 
                                       ])    
        let script1 = OpenFile(project,"Script1.fsx")   
        TakeCoffeeBreak(this.VS)
        AssertNoErrorsOrWarnings(project)
        
    // #load of .fsi is respected (for non-hidden property)
    [<Fact>]
    member public this.``Fsx.NoError.ScriptClosure.TransitiveLoad9``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1fsi = AddFileFromText(project,"File1.fsi",
                                      ["namespace Namespace"
                                       "type Foo ="
                                       "  class"
                                       "    static member Property : int" // Not exposing 'HiddenProperty'
                                       "  end"
                                       ])            
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public HiddenProperty = 0"
                                     "     static member public Property = 0"
                                     ])    

        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"File1.fsi\""
                                       "#load \"File1.fs\""
                                       "Namespace.Foo.Property" 
                                       ])     
        let script1 = OpenFile(project,"Script1.fsx")   
        AssertNoErrorsOrWarnings(project) 

    // #load of .fsi is respected at second #load level (for non-hidden property) 
    [<Fact>]
    member public this.``Fsx.NoError.ScriptClosure.TransitiveLoad10``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1fsi = AddFileFromText(project,"File1.fsi",
                                      ["namespace Namespace"
                                       "type Foo ="
                                       "  class"
                                       "    static member Property : int" // Not exposing 'HiddenProperty'
                                       "  end"
                                       ])            
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public HiddenProperty = 0"
                                     "     static member public Property = 0"
                                     ])    

        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"File1.fsi\""
                                       "#load \"File1.fs\""
                                       ])     
        let script2 = AddFileFromText(project,"Script2.fsx",
                                      ["#load \"Script1.fsx\""
                                       "Namespace.Foo.Property" 
                                       ])    
        let script2 = OpenFile(project,"Script2.fsx")   
        AssertNoErrorsOrWarnings(project) 

    // #load of .fsi is respected when dispersed between two #load levels (for non-hidden property)
    [<Fact>]
    member public this.``Fsx.NoError.ScriptClosure.TransitiveLoad11``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1fsi = AddFileFromText(project,"File1.fsi",
                                      ["namespace Namespace"
                                       "type Foo ="
                                       "  class"
                                       "    static member Property : int" // Not exposing 'HiddenProperty'
                                       "  end"
                                       ])            
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public HiddenProperty = 0"
                                     "     static member public Property = 0"
                                     ])    

        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"File1.fsi\""
                                       ])     
        let script2 = AddFileFromText(project,"Script2.fsx",
                                      ["#load \"Script1.fsx\""
                                       "#load \"File1.fs\""
                                       "Namespace.Foo.Property" 
                                       ])    
        let script2 = OpenFile(project,"Script2.fsx")   
        AssertNoErrorsOrWarnings(project)  
        
    // #load of .fsi is respected when dispersed between two #load levels (the other way) (for non-hidden property)
    [<Fact>]
    member public this.``Fsx.NoError.ScriptClosure.TransitiveLoad12``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1fsi = AddFileFromText(project,"File1.fsi",
                                      ["namespace Namespace"
                                       "type Foo ="
                                       "  class"
                                       "    static member Property : int" // Not exposing 'HiddenProperty'
                                       "  end"
                                       ])            
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public HiddenProperty = 0"
                                     "     static member public Property = 0"
                                     ])    

        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"File1.fs\""
                                       ])     
        let script2 = AddFileFromText(project,"Script2.fsx",
                                      ["#load \"File1.fsi\""
                                       "#load \"Script1.fsx\""
                                       "Namespace.Foo.Property" 
                                       ])    
        let script2 = OpenFile(project,"Script2.fsx")   
        AssertNoErrorsOrWarnings(project)  
        
    // #nowarn seen in closed .fsx is global to the closure
    [<Fact>]
    member public this.``Fsx.NoError.ScriptClosure.TransitiveLoad16``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let thisProject = AddFileFromText(project,"ThisProject.fsx",
                                      ["#nowarn \"44\""
                                       ])  
        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"ThisProject.fsx\"" // Should bring in #nowarn "44" so we don't see this warning:
                                       "[<System.Obsolete(\"x\")>]"
                                       "let fn x = 0"
                                       "let y = fn 1"
                                       ])                                                   

        let script1 = OpenFile(project,"Script1.fsx")   
        MoveCursorToEndOfMarker(script1,"let y = f") 
        TakeCoffeeBreak(this.VS)
        AssertExactlyOneErrorSeenContaining(project, "This construct is deprecated. x")  // This is expected for langVersion >= 10.0

    /// FEATURE: #r in .fsx to a .dll name works.
    [<Fact>]
    member public this.``Fsx.NoError.HashR.DllWithNoPath``() =  
        let fileContent = """
            #light
            #r "System.Transactions.dll"
            open System.Transactions"""
        this.VerifyFSXNoErrorList(fileContent)


    [<Fact>]
    // 'System' is in the default set. Make sure we can still resolve it.
    member public this.``Fsx.NoError.HashR.BugDefaultReferenceFileIsAlsoResolved``() =  
        let fileContent = """
            #light
            #r "System"
            """
        this.VerifyFSXNoErrorList(fileContent)

    [<Fact>]
    member public this.``Fsx.NoError.HashR.DoubleReference``() =  
        let fileContent = """
            #light
            #r "System"
            #r "System"
            """
        this.VerifyFSXNoErrorList(fileContent)

    [<Fact>]
    // 'CustomMarshalers' is loaded from the GAC _and_ it is available on XP and above.
    member public this.``Fsx.NoError.HashR.ResolveFromGAC``() =  
        let fileContent = """
            #light
            #r "CustomMarshalers"
            """
        this.VerifyFSXNoErrorList(fileContent)

    [<Fact>]
    member public this.``Fsx.NoError.HashR.ResolveFromFullyQualifiedPath``() =
        let fullyqualifiepathtoddll = System.IO.Path.Combine( System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(), "System.configuration.dll" )
        let code = ["#light";"#r @\"" + fullyqualifiepathtoddll + "\""]
        let (project, _) = createSingleFileFsxFromLines code
        AssertNoErrorsOrWarnings(project)

    [<Fact(Skip = "Re-enable this test --- https://github.com/dotnet/fsharp/issues/5238")>]
    member public this.``Fsx.NoError.HashR.RelativePath1``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let file1 = AddFileFromText(project,"lib.fs",
                                    ["module Lib"
                                     "let X = 42"
                                     ])
        
        let bld = Build(project)

        let script1Dir = Path.Combine(ProjectDirectory(project), "ccc")
        let script1Path = Path.Combine(script1Dir, "Script1.fsx")
        let script2Dir = Path.Combine(ProjectDirectory(project), "aaa\\bbb")
        let script2Path = Path.Combine(script2Dir, "Script2.fsx")
        
        Directory.CreateDirectory(script1Dir) |> ignore
        Directory.CreateDirectory(script2Dir) |> ignore
        File.Move(bld.ExecutableOutput, Path.Combine(ProjectDirectory(project), "aaa\\lib.exe"))

        let script1 = File.WriteAllLines(script1Path,
                                      ["#load \"../aaa/bbb/Script2.fsx\""
                                       "printfn \"%O\" Lib.X"
                                       ])
        let script2 = File.WriteAllLines(script2Path,
                                      ["#r \"../lib.exe\""
                                       ])

        let script1 = OpenFile(project, script1Path)   
        TakeCoffeeBreak(this.VS)
        
        MoveCursorToEndOfMarker(script1,"#load")
        let ans = GetSquiggleAtCursor(script1)
        AssertNoSquiggle(ans)

    [<Fact(Skip = "Re-enable this test --- https://github.com/dotnet/fsharp/issues/5238")>]
    member public this.``Fsx.NoError.HashR.RelativePath2``() = 
        use _guard = this.UsingNewVS()  
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let file1 = AddFileFromText(project,"lib.fs",
                                    ["module Lib"
                                     "let X = 42"
                                     ])

        let bld = Build(project)

        let script1Dir = Path.Combine(ProjectDirectory(project), "ccc")
        let script1Path = Path.Combine(script1Dir, "Script1.fsx")
        let script2Dir = Path.Combine(ProjectDirectory(project), "aaa")
        let script2Path = Path.Combine(script2Dir, "Script2.fsx")
        
        Directory.CreateDirectory(script1Dir) |> ignore
        Directory.CreateDirectory(script2Dir) |> ignore
        File.Move(bld.ExecutableOutput, Path.Combine(ProjectDirectory(project), "aaa\\lib.exe"))

        let script1 = File.WriteAllLines(script1Path,
                                      ["#load \"../aaa/Script2.fsx\""
                                       "printfn \"%O\" Lib.X"
                                       ])
        let script2 = File.WriteAllLines(script2Path,
                                      ["#r \"lib.exe\""
                                       ])
                                       
        let script1 = OpenFile(project, script1Path)   
        TakeCoffeeBreak(this.VS)
        
        MoveCursorToEndOfMarker(script1,"#load")
        let ans = GetSquiggleAtCursor(script1)
        AssertNoSquiggle(ans)

     /// FEATURE: #load in an .fsx file will include that file in the 'build' of the .fsx.
    [<Fact>]
    member public this.``Fsx.NoError.HashLoad.Simple``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fs = AddFileFromText(project,"File1.fs",
                                    ["#light"
                                     "namespace MyNamespace" 
                                     "    module MyModule ="
                                     "        let x = 1" 
                                     ])            
        
        let fsx = AddFileFromText(project,"File2.fsx",
                                    ["#light"
                                     "#load \"File1.fs\"" 
                                     "open MyNamespace.MyModule"
                                     "printfn \"%d\" x"
                                     ])    
        let fsx = OpenFile(project,"File2.fsx")    
        AssertNoErrorsOrWarnings(project)

    // In this bug the #loaded file contains a level-4 warning (copy to avoid mutation). This warning was reported at the #load in file2.fsx but shouldn't have been.s
    [<Fact>]
    member public this.``Fsx.NoWarn.OnLoadedFile.Bug4837``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fs = AddFileFromText(project,"File1.fs",
                                    ["module File1Module"
                                     "let x = System.DateTime.Now - System.DateTime.Now"
                                     "x.Add(x) |> ignore" 
                                     ])            
        
        let fsx = AddFileFromText(project,"File2.fsx",
                                    [
                                     "#load \"File1.fs\"" 
                                     ])    
        let fsx = OpenFile(project,"File2.fsx")    
        AssertNoErrorsOrWarnings(project) 

    /// FEATURE: .fsx files have automatic imports of certain system assemblies.
    //There is a test bug here. The actual scenario works. Need to revisit.
    [<Fact>]
    member public this.``Fsx.NoError.AutomaticImportsForFsxFiles``() =
        let fileContent = """
            #light
            open System
            open System.Xml
            open System.Drawing
            open System.Runtime.Remoting
            open System.Runtime.Serialization.Formatters.Soap
            open System.Data
            open System.Drawing
            open System.Web
            open System.Web.Services
            open System.Windows.Forms"""
        this.VerifyFSXNoErrorList(fileContent) 

    // Corecursive load to nonexisting property.
    [<Fact>]
    member public this.``Fsx.ExactlyOneError.ScriptClosure.TransitiveLoad4``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public Property = 0"
                                     ])    
        let script2 = AddFileFromText(project,"Script2.fsx",
                                      ["#load \"Script1.fsx\""
                                       "#load \"File1.fs\""
                                       ])    
        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"Script2.fsx\""
                                       "#load \"File1.fs\""
                                       "Namespace.Foo.NonExistingProperty" 
                                       ])     
        let script1 = OpenFile(project,"Script1.fsx")   
        AssertExactlyOneErrorSeenContaining(project, "NonExistingProperty")  

    // #load of .fsi is respected
    [<Fact>]
    member public this.``Fsx.ExactlyOneError.ScriptClosure.TransitiveLoad5``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1fsi = AddFileFromText(project,"File1.fsi",
                                      ["namespace Namespace"
                                       "type Foo ="
                                       "  class"
                                       "    static member Property : int" // Not exposing 'HiddenProperty'
                                       "  end"
                                       ])            
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public HiddenProperty = 0"
                                     "     static member public Property = 0"
                                     ])    

        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"File1.fsi\""
                                       "#load \"File1.fs\""
                                       "Namespace.Foo.HiddenProperty" 
                                       ])     
        let script1 = OpenFile(project,"Script1.fsx")   
        AssertExactlyOneErrorSeenContaining(project, "HiddenProperty")   

    // #load of .fsi is respected at second #load level
    [<Fact>]
    member public this.``Fsx.ExactlyOneError.ScriptClosure.TransitiveLoad6``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1fsi = AddFileFromText(project,"File1.fsi",
                                      ["namespace Namespace"
                                       "type Foo ="
                                       "  class"
                                       "    static member Property : int" // Not exposing 'HiddenProperty'
                                       "  end"
                                       ])            
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public HiddenProperty = 0"
                                     "     static member public Property = 0"
                                     ])    

        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"File1.fsi\""
                                       "#load \"File1.fs\""
                                       ])     
        let script2 = AddFileFromText(project,"Script2.fsx",
                                      ["#load \"Script1.fsx\""
                                       "Namespace.Foo.HiddenProperty" 
                                       ])    
        let script2 = OpenFile(project,"Script2.fsx")   
        AssertExactlyOneErrorSeenContaining(project, "HiddenProperty") 
        
    // #load of .fsi is respected when dispersed between two #load levels
    [<Fact>]
    member public this.``Fsx.ExactlyOneError.ScriptClosure.TransitiveLoad7``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1fsi = AddFileFromText(project,"File1.fsi",
                                      ["namespace Namespace"
                                       "type Foo ="
                                       "  class"
                                       "    static member Property : int" // Not exposing 'HiddenProperty'
                                       "  end"
                                       ])            
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public HiddenProperty = 0"
                                     "     static member public Property = 0"
                                     ])    

        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"File1.fsi\""
                                       ])     
        let script2 = AddFileFromText(project,"Script2.fsx",
                                      ["#load \"Script1.fsx\""
                                       "#load \"File1.fs\""
                                       "Namespace.Foo.HiddenProperty" 
                                       ])    
        let script2 = OpenFile(project,"Script2.fsx")   
        AssertExactlyOneErrorSeenContaining(project, "HiddenProperty")    
        
    // #load of .fsi is respected when dispersed between two #load levels (the other way)
    [<Fact>]
    member public this.``Fsx.ExactlyOneError.ScriptClosure.TransitiveLoad8``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file1fsi = AddFileFromText(project,"File1.fsi",
                                      ["namespace Namespace"
                                       "type Foo ="
                                       "  class"
                                       "    static member Property : int" // Not exposing 'HiddenProperty'
                                       "  end"
                                       ])            
        let file1 = AddFileFromText(project,"File1.fs",
                                    ["namespace Namespace"
                                     "type Foo = "
                                     "     static member public HiddenProperty = 0"
                                     "     static member public Property = 0"
                                     ])    

        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"File1.fs\""
                                       ])     
        let script2 = AddFileFromText(project,"Script2.fsx",
                                      ["#load \"File1.fsi\""
                                       "#load \"Script1.fsx\""
                                       "Namespace.Foo.HiddenProperty" 
                                       ])    
        let script2 = OpenFile(project,"Script2.fsx")   
        AssertExactlyOneErrorSeenContaining(project, "HiddenProperty")   
        
    // Bug seen during development: A #load in an .fs would be followed.
    [<Fact>]
    member public this.``Fsx.ExactlyOneError.ScriptClosure.TransitiveLoad15``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let file2 = AddFileFromText(project,"File2.fs",
                                      ["namespace Namespace"
                                       "type Type() ="
                                       "    static member Property = 0"
                                       ])  
        let file1 = AddFileFromText(project,"File1.fs",
                                      ["#load \"File2.fs\""  // This is not allowed but it was working anyway.
                                       "namespace File2Namespace"
                                       ])                                              
        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"File1.fs\""
                                       "Namespace.Type.Property"
                                       ])                                                   

        let script1 = OpenFile(project,"Script1.fsx")   
        TakeCoffeeBreak(this.VS)
        AssertExactlyOneErrorSeenContaining(project, "Namespace")  

    [<Fact>]
    member public this.``Fsx.Bug4311HoverOverReferenceInFirstLine``() =
        let fileContent = """#r "PresentationFramework.dll"
                             
                             #r "PresentationCore.dll" """
        let marker = "#r \"PresentationFrame"
        this.AssertQuickInfoContainsAtEndOfMarkerInFsxFile fileContent marker "PresentationFramework.dll"
        this.AssertQuickInfoNotContainsAtEndOfMarkerInFsxFile fileContent marker "multiple results"

    [<Fact>]
    member public this.``Fsx.QuickInfo.Bug4979``() =
        let code = 
                ["System.ConsoleModifiers.Shift |> ignore "
                 "(3).ToString().Length |> ignore "]
        let (project, file) = createSingleFileFsxFromLines code
        MoveCursorToEndOfMarker(file, "System.ConsoleModifiers.Sh")
        let tooltip = GetQuickInfoAtCursor file
        AssertContains(tooltip, @"<summary>The left or right SHIFT modifier key.</summary>")    
        
        MoveCursorToEndOfMarker(file, "(3).ToString().Len")
        let tooltip = GetQuickInfoAtCursor file
        AssertContains(tooltip, @"[Signature:P:System.String.Length]") // A message from the mock IDocumentationBuilder
        AssertContains(tooltip, @"[Filename:") 
        AssertContains(tooltip, @"netstandard.dll]") // The assembly we expect the documentation to get taken from  

    // Especially under 4.0 we need #r of .NET framework assemblies to resolve from like,
    //
    //      %program files%\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0
    //
    // because this is where the .XML files are.
    //
    // When executing scripts, however, we need to _not_ resolve from these directories because
    // they may be metadata-only assemblies.
    //
    // "Reference Assemblies" was only introduced in 3.5sp1, so not all 2.0 F# boxes will have it, so only run on 4.0
    [<Fact>]
    member public this.``Fsx.Bug5073``() =
        let fileContent = """#r "System" """
        let marker = "#r \"System"
        this.AssertQuickInfoContainsAtEndOfMarkerInFsxFile fileContent marker @"Reference Assemblies\Microsoft"
        this.AssertQuickInfoContainsAtEndOfMarkerInFsxFile fileContent marker ".NET Framework"

    /// FEATURE: Hovering over a resolved #r file will show a data tip with the fully qualified path to that file.
    [<Fact>]
    member public this.``Fsx.HashR_QuickInfo.ShowFilenameOfResolvedAssembly``() =
        this.AssertQuickInfoContainsAtEndOfMarkerInFsxFile
            """#r "System.Transactions" """ // Pick anything that isn't in the standard set of assemblies.
            "#r \"System.Tra" "System.Transactions.dll"

    [<Fact>]
    member public this.``Fsx.HashR_QuickInfo.BugDefaultReferenceFileIsAlsoResolved``() =
        this.AssertQuickInfoContainsAtEndOfMarkerInFsxFile
            """#r "System" """  // 'System' is in the default set. Make sure we can still resolve it.
            "#r \"Syst" "System.dll"
        
    [<Fact>]
    member public this.``Fsx.HashR_QuickInfo.DoubleReference``() =
        let fileContent = """#r "System" // Mark1
                             #r "System" // Mark2 """   // The same reference repeated twice. 
        this.AssertQuickInfoContainsAtStartOfMarkerInFsxFile fileContent "tem\" // Mark1" "System.dll"
        this.AssertQuickInfoContainsAtStartOfMarkerInFsxFile fileContent "tem\" // Mark2" "System.dll"
        
    [<Fact>]
    member public this.``Fsx.HashR_QuickInfo.ResolveFromGAC``() = 
        this.AssertQuickInfoContainsAtEndOfMarkerInFsxFile
            """#r "CustomMarshalers" """        // 'mscorcfg' is loaded from the GAC _and_ it is available on XP and above.
            "#r \"Custo" ".NET Framework"

    [<Fact>]
    member public this.``Fsx.HashR_QuickInfo.ResolveFromFullyQualifiedPath``() = 
        let fullyqualifiepathtoddll = System.IO.Path.Combine( System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(), "System.configuration.dll" ) // Can be any fully qualified path to an assembly
        let expectedtooltip = System.Reflection.Assembly.ReflectionOnlyLoadFrom(fullyqualifiepathtoddll).FullName
        let fileContent = "#r @\"" + fullyqualifiepathtoddll + "\""
        let marker = "#r @\"" + fullyqualifiepathtoddll.Substring(0,fullyqualifiepathtoddll.Length/2)       // somewhere in the middle of the string
        this.AssertQuickInfoContainsAtEndOfMarkerInFsxFile fileContent marker expectedtooltip
        //this.AssertQuickInfoNotContainsAtEndOfMarkerInFsxFile fileContent marker ".dll"

    [<Fact>]
    member public this.``Fsx.InvalidHashReference.ShouldBeASquiggle.Bug3012``() =  
        let code = 
            ["#light"
             "#r \"Bar.dll\""]
        let (project, file) = createSingleFileFsxFromLines code
        MoveCursorToEndOfMarker(file,"#r \"Ba") 
        let squiggle = GetSquiggleAtCursor(file)
        TakeCoffeeBreak(this.VS)
        Assert.True(snd squiggle.Value |> fun str -> str.Contains("Bar.dll"))

    // Bug seen during development: The unresolved reference error would x-ray through to the root.
    [<Fact>]
    member public this.``Fsx.ScriptClosure.TransitiveLoad14``() =  
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")    
        let script2 = AddFileFromText(project,"Script2.fsx",
                                      ["#load \"Script1.fsx\""
                                       "#r \"NonExisting\""
                                       ])      
        let script1 = AddFileFromText(project,"Script1.fsx",
                                      ["#load \"Script2.fsx\""
                                       "#r \"System\""
                                       ])                                                   

        let script1 = OpenFile(project,"Script1.fsx")   
        TakeCoffeeBreak(this.VS)      
        MoveCursorToEndOfMarker(script1,"#r \"Sys") 
        AssertEqual(None,GetSquiggleAtCursor(script1))
    
    member private this.TestFsxHashDirectivesAreErrors(mark : string, expectedStr : string) = 
        let code = 
                                    ["#light"
                                     "#r \"JoeBob\""
                                     "#I \".\""
                                     "#load \"Dooby\""
                                     ]
        let (_, _, file) = this.CreateSingleFileProject(code)
        MoveCursorToEndOfMarker(file,mark) 
        let ans = GetSquiggleAtCursor(file)
        match ans with
        | Some(sev,msg) -> 
            AssertEqual(Microsoft.VisualStudio.FSharp.LanguageService.Severity.Error, sev)
            AssertContains(msg, expectedStr)
        | _ -> failwith ""

    /// FEATURE: #r, #I, #load are all errors when running under the language service
    [<Fact>]
    member public this.``Fsx.HashDirectivesAreErrors.InNonScriptFiles.Case1``() =  
        this.TestFsxHashDirectivesAreErrors("#r \"Joe", "may only be used in F# script files")
     
    [<Fact>]
    member public this.``Fsx.HashDirectivesAreErrors.InNonScriptFiles.Case2``() =  
        this.TestFsxHashDirectivesAreErrors("#I \"", "may only be used in F# script files")
        
    [<Fact>]
    member public this.``Fsx.HashDirectivesAreErrors.InNonScriptFiles.Case3``() =  
        this.TestFsxHashDirectivesAreErrors("#load \"Doo", "may only be used in F# script files")

    /// FEATURE: #reference against a non-assembly .EXE gives a reasonable error message
    //[<Fact>]
    member public this.``Fsx.HashReferenceAgainstNonAssemblyExe``() = 
        let windows = System.Environment.GetEnvironmentVariable("windir")
        let code =
                                    ["#light"
                                     sprintf "#reference @\"%s\"" (Path.Combine(windows,"notepad.exe"))
                                     "    let x = 1"]
        let (_, file) = createSingleFileFsxFromLines code
        
        MoveCursorToEndOfMarker(file,"#refe")
        let ans = GetSquiggleAtCursor(file)
        AssertSquiggleIsErrorContaining(ans, "was not found or is invalid")

    (* ---------------------------------------------------------------------------------- *)

     // FEATURE: A #loaded file is squiggled with an error if there are errors in that file.
    [<Fact>]
    member public this.``Fsx.HashLoadedFileWithErrors.Bug3149``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        
        let file1 = AddFileFromText(project,"File1.fs", 
                                    ["#light"
                                     "module File1" 
                                     "DogChow" // <-- error
                                    ])

        let file2 = AddFileFromText(project,"File2.fsx",
                                    ["#light"
                                     "#load @\"File1.fs\""
                                     ])
        let file2 = OpenFile(project,"File2.fsx")
        
        MoveCursorToEndOfMarker(file2,"#load @\"Fi")
        TakeCoffeeBreak(this.VS)        
        let ans = GetSquiggleAtCursor(file2)    
        AssertSquiggleIsErrorContaining(ans, "DogChow")      
        
        
     // FEATURE: A #loaded file is squiggled with a warning if there are warning that file.
    [<Fact>]
    member public this.``Fsx.HashLoadedFileWithWarnings.Bug3149``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        
        let file1 = AddFileFromText(project,"File1.fs", 
                                    ["module File1Module"
                                     "type WarningHere<'a> = static member X() = 0"
                                     "let y = WarningHere.X"
                                    ])

        let file2 = AddFileFromText(project,"File2.fsx",
                                    ["#light"
                                     "#load @\"File1.fs\""
                                     ])
        let file2 = OpenFile(project,"File2.fsx")
        
        MoveCursorToEndOfMarker(file2,"#load @\"Fi")
        let ans = GetSquiggleAtCursor(file2)
        AssertSquiggleIsWarningContaining(ans, "WarningHere")  

     // Bug: #load should report the first error message from a file
    [<Fact>]
    member public this.``Fsx.HashLoadedFileWithErrors.Bug3652``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        
        let file1 = AddFileFromText(project,"File1.fs", 
                                    [ "#light"
                                      "module File1" 
                                      "let a = 1 + \"\""
                                      "let c = new obj()"
                                      "let b = c.foo()"
                                    ])
        let file2 = AddFileFromText(project,"File2.fsx",
                                    ["#light"
                                     "#load @\"File1.fs\""
                                     ])
        let file2 = OpenFile(project,"File2.fsx")
       
        MoveCursorToEndOfMarker(file2,"#load @\"Fi")
        let ans = GetSquiggleAtCursor(file2)
        AssertSquiggleIsErrorContaining(ans, "'string'")
        AssertSquiggleIsErrorContaining(ans, "'int'")
        AssertSquiggleIsErrorNotContaining(ans, "foo")

    // In this bug the .fsx project directory was wrong so it couldn't reference a relative file.
    [<Fact>]
    member public this.``Fsx.ScriptCanReferenceBinDirectoryOutput.Bug3151``() =
        use _guard = this.UsingNewVS()
        let stopWatch = new System.Diagnostics.Stopwatch()
        let ResetStopWatch() = stopWatch.Reset(); stopWatch.Start()
        let time1 op a message = 
            ResetStopWatch()
            let result = op a
            printf "%s %d ms\n" message stopWatch.ElapsedMilliseconds
            result
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let file1 = AddFileFromText(project,"File1.fs", ["#light"])
        let projectOutput = time1 Build project "Time to build project"
        printfn "Output of building project was %s" projectOutput.ExecutableOutput
        printfn "Project directory is %s" (ProjectDirectory project)
        
        let file2 = AddFileFromText(project,"File2.fsx",
                                    ["#light"
                                     "#reference @\"bin\\Debug\\testproject.exe\""
                                     ])
        let file2 = OpenFile(project,"File2.fsx")
        
        MoveCursorToEndOfMarker(file2,"#reference @\"bin\\De")
        let ans = GetSquiggleAtCursor(file2)
        AssertNoSquiggle(ans)

               

    /// In this bug, multiple references to mscorlib .dll were causing problem in load closure
    [<Fact>]
    member public this.``Fsx.BugAllowExplicitReferenceToMsCorlib``() =  
        let code =
                                    ["#r \"mscorlib\""
                                     "fsi."
                                     ]
        let (_, file) = createSingleFileFsxFromLines code
        MoveCursorToEndOfMarker(file,"fsi.")
        TakeCoffeeBreak(this.VS)
        let completions = AutoCompleteAtCursor file
        AssertCompListContains(completions,"CommandLineArgs")        
        
    /// FEATURE: There is a global fsi module that should be in scope for script files.        
    [<Fact>]
    member public this.``Fsx.Bug2530FsiObject``() =  
        let code = 
                                    ["#light"
                                     "fsi."
                                     ]
        let (_, file) = createSingleFileFsxFromLines code
        MoveCursorToEndOfMarker(file,"fsi.")
        TakeCoffeeBreak(this.VS)
        let completions = AutoCompleteAtCursor file
        AssertCompListContains(completions,"CommandLineArgs")
  
    // Ensure that the script closure algorithm gets the right order of hash directives
    [<Fact>]
    member public this.``Fsx.ScriptClosure.SurfaceOrderOfHashes``() =  
        let code = 
                                    ["#r \"System.Runtime.Remoting\""
                                     "#r \"System.Transactions\""
                                     "#load \"Load1.fs\""
                                     "#load \"Load2.fsx\""
                                     ]
        let (project, file) = createSingleFileFsxFromLines code
        let projectFolder = ProjectDirectory(project)
        let fas = GetProjectOptionsOfScript(file)
        AssertArrayContainsPartialMatchOf(fas.OtherOptions, "--noframework")
        AssertArrayContainsPartialMatchOf(fas.OtherOptions, "System.Runtime.Remoting.dll")
        AssertArrayContainsPartialMatchOf(fas.OtherOptions, "System.Transactions.dll")
        AssertArrayContainsPartialMatchOf(fas.OtherOptions, "FSharp.Compiler.Interactive.Settings.dll")
        Assert.Equal(Path.Combine(projectFolder,"File1.fsx"), fas.SourceFiles.[0])
        Assert.Equal(1, fas.SourceFiles.Length)


    /// FEATURE: #reference against a strong name should work.
    [<Fact>]
    member public this.``Fsx.HashReferenceAgainstStrongName``() = 
        let code =
                                            ["#light"
                                             sprintf "#reference \"System.Core, Version=%s, Culture=neutral, PublicKeyToken=b77a5c561934e089\"" (System.Environment.Version.ToString())
                                             "open System."]
        let (_, file) = createSingleFileFsxFromLines code
        MoveCursorToEndOfMarker(file,"open System.") 
        let completions = AutoCompleteAtCursor file
        AssertCompListContains(completions,"Linq")   


    /// Try out some bogus file names in #r, #I and #load.
    [<Fact>]
    member public this.``Fsx.InvalidMetaCommandFilenames``() =
        let code = 
                                    [
                                     "#r @\"\""
                                     "#load @\"\""
                                     "#I @\"\""
                                     "#r @\"*\""
                                     "#load @\"*\""
                                     "#I @\"*\""
                                     "#r @\"?\""
                                     "#load @\"?\""
                                     "#I @\"?\""
                                     """#r @"C:\path\does\not\exist.dll"  """
                                    ]
        let (_, file) = createSingleFileFsxFromLines code
        TakeCoffeeBreak(this.VS) // This used to assert

    /// FEATURE: .fsx files have INTERACTIVE #defined
    [<Fact>]
    member public this.``Fsx.INTERACTIVEIsDefinedInFsxFiles``() =
        let code =
                                    [
                                     "#if INTERACTIVE"
                                     "let xyz = 1"
                                     "#endif"
                                    ]
        let (_, file) = createSingleFileFsxFromLines code
        MoveCursorToEndOfMarker(file,"let xy")
        AssertEqual(TokenType.Identifier ,GetTokenTypeAtCursor(file))  

    // Ensure that basic compile of an .fsx works        
    [<Fact>]
    member public this.``Fsx.CompileFsx_1``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        
        let file1 = AddFileFromTextEx(project,"Script.fsx","Script.fsx",BuildAction.Compile,
                                      ["printfn \"Hello world\""])
        let build = time1 Build project "Time to build project"
        Assert.True(build.BuildSucceeded, "Expected build to succeed")
        ShowErrors(project)
        

    // Compile a script which #loads a source file. The build can't succeed without the loaded file.      
    [<Fact>]
    member public this.``Fsx.CompileFsx_2``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fs = AddFileFromTextEx(project,"File.fs","File.fs",BuildAction.Compile,
                                      ["namespace Namespace"
                                       "module Module ="
                                       "  let Value = 1"
                                      ])
        let fsx = AddFileFromTextEx(project,"Script.fsx","Script.fsx",BuildAction.Compile,
                                      ["#load \"File.fs\""
                                       "printfn \"%d\" Namespace.Module.Value"])
        let build = time1 Build project "Time to build project"
        if SupportsOutputWindowPane(this.VS) then 
            let lines = GetOutputWindowPaneLines(this.VS)
            for line in lines do printfn "%s" line
            ()
        Assert.True(build.BuildSucceeded, "Expected build to succeed")
        
    // Compile a script which #loads a source file. The build can't succeed without 
    [<Fact>]
    member public this.``Fsx.CompileFsx_3``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fs = AddFileFromTextEx(project,"File.fs","File.fs",BuildAction.None,
                                      ["namespace Namespace"
                                       "module Module ="
                                       "  let Value = 1"
                                      ])
        let fsx = AddFileFromTextEx(project,"Script.fsx","Script.fsx",BuildAction.Compile,
                                      ["#load \"File.fs\""
                                       "printfn \"%d\" Namespace.Module.Value"])
        let build = time1 Build project "Time to build project" 
        if SupportsOutputWindowPane(this.VS) then 
            let lines = GetOutputWindowPaneLines(this.VS)
            for line in lines do printfn "%s" line
            ()
        Assert.True(build.BuildSucceeded, "Expected build to succeed")        
        
    // Must be explicitly referenced by compile.
    [<Fact>]
    member public this.``Fsx.CompileFsx_Bug5416_1``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fsx = AddFileFromTextEx(project,"Script.fsx","Script.fsx",BuildAction.Compile,
                                      ["let x = fsi.CommandLineArgs"])
        let build = time1 Build project "Time to build project" 
        if SupportsOutputWindowPane(this.VS) then 
            let lines = GetOutputWindowPaneLines(this.VS)
            for line in lines do printfn "%s" line
            ()
        Assert.True(not(build.BuildSucceeded), "Expected build to fail")    
        
    // Must be explicitly referenced by compile.
    [<Fact(Skip = "Bug https://github.com/dotnet/fsharp/issues/17330")>]
    member public this.``Fsx.CompileFsx_Bug5416_2``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let binariesFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
        PlaceIntoProjectFileBeforeImport
            (project, sprintf @"
                <ItemGroup>
                    <!-- Subtle: You need this reference to compile but not to get language service -->
                    <Reference Include=""FSharp.Compiler.Interactive.Settings"">
                        <HintPath>%s\\FSharp.Compiler.Interactive.Settings.dll</HintPath>
                    </Reference>
                    <Reference Include=""FSharp.Compiler.Service"">
                        <HintPath>%s\\FSharp.Compiler.Service.dll</HintPath>
                    </Reference>
                </ItemGroup>" binariesFolder binariesFolder)

        let fsx = AddFileFromTextEx(project,"Script.fsx","Script.fsx",BuildAction.Compile,
                                      ["let x = fsi.CommandLineArgs"])
        let build = time1 Build project "Time to build project" 
        if SupportsOutputWindowPane(this.VS) then 
            let lines = GetOutputWindowPaneLines(this.VS)
            for line in lines do printfn "%s" line
            ()
        if not(SupportsOutputWindowPane(this.VS)) then  
            Assert.True(build.BuildSucceeded, "Expected build to succeed")                
        
        
    // Ensure that #load order is preserved when #loading multiple files. 
    [<Fact>]
    member public this.``Fsx.CompileFsx_5``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fs1 = AddFileFromTextEx(project,"File1.fs","File1.fs",BuildAction.None,
                                      ["namespace Namespace"
                                       "module Module1 ="
                                       "  let Value = 1"
                                      ])
        let fs2 = AddFileFromTextEx(project,"File2.fs","File2.fs",BuildAction.None,
                                      ["namespace Namespace"
                                       "module Module2 ="
                                       "  let Value = Module1.Value"
                                      ])                                      
        let fsx = AddFileFromTextEx(project,"Script.fsx","Script.fsx",BuildAction.Compile,
                                      [
                                       "#load \"File1.fs\""
                                       "#load \"File2.fs\""
                                       "printfn \"%d\" Namespace.Module2.Value"])
        let build = time1 Build project "Time to build project" 
        if SupportsOutputWindowPane(this.VS) then 
            let lines = GetOutputWindowPaneLines(this.VS)
            for line in lines do printfn "%s" line
            ()
        Assert.True(build.BuildSucceeded, "Expected build to succeed")          
        
    // If an fs file is explicitly passed in to the compiler and also #loaded then 
    // the command-line order is respected rather than the #load order
    [<Fact>]
    member public this.``Fsx.CompileFsx_6``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fs1 = AddFileFromTextEx(project,"File1.fs","File1.fs",BuildAction.Compile,
                                      ["namespace Namespace"
                                       "module Module1 ="
                                       "  let Value = 1"
                                      ])
        let fs2 = AddFileFromTextEx(project,"File2.fs","File2.fs",BuildAction.Compile,
                                      ["namespace Namespace"
                                       "module Module2 ="
                                       "  let Value = Module1.Value"
                                      ])                                      
        let fsx = AddFileFromTextEx(project,"Script.fsx","Script.fsx",BuildAction.Compile,
                                      [
                                       "#load \"File2.fs\"" // Wrong order
                                       "#load \"File1.fs\""
                                       "printfn \"%d\" Namespace.Module2.Value"])
        let build = time1 Build project "Time to build project" 
        if SupportsOutputWindowPane(this.VS) then 
            let lines = GetOutputWindowPaneLines(this.VS)
            for line in lines do printfn "%s" line
            ()
        Assert.True(build.BuildSucceeded, "Expected build to succeed") 

        
        
    // If a #loaded file does not exist, there should be an error
    [<Fact>]
    member public this.``Fsx.CompileFsx_7``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fsx = AddFileFromTextEx(project,"Script.fsx","Script.fsx",BuildAction.Compile,
                                      [
                                       "#load \"NonexistentFile.fs\""
                                       ])
        let build = time1 Build project "Time to build project" 
        if SupportsOutputWindowPane(this.VS) then 
            let lines = GetOutputWindowPaneLines(this.VS)
            for line in lines do printfn "%s" line
            AssertListContainsInOrder(lines, ["error FS0079: Could not load file"; "NonexistentFile.fs"; "because it does not exist or is inaccessible"])            
            
        Assert.True(not(build.BuildSucceeded), "Expected build to fail")       
        
        
    // #r references should be respected.
    [<Fact>]
    member public this.``Fsx.CompileFsx_8``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fsx = AddFileFromTextEx(project,"Script.fsx","Script.fsx",BuildAction.Compile,
                                      [
                                       "#r \"System.Messaging\""
                                       "let a = new System.Messaging.AccessControlEntry()"
                                       ])
        let build = time1 Build project "Time to build project" 
        if SupportsOutputWindowPane(this.VS) then 
            let lines = GetOutputWindowPaneLines(this.VS)
            for line in lines do printfn "%s" line
            
        Assert.True(build.BuildSucceeded, "Expected build to succeed")          
        
        
    // Missing script file should be a reasonable failure, not a callstack.
    [<Fact>]
    member public this.``Fsx.CompileFsx_Bug5414``() =
        use _guard = this.UsingNewVS()
        let solution = this.CreateSolution()
        let project = CreateProject(solution,"testproject")
        let fsx = AddFileFromTextEx(project,"Script.fsx","Script.fsx",BuildAction.Compile,[])
        DeleteFileFromDisk(this.VS, fsx) 
        
        let build = Build project
        if SupportsOutputWindowPane(this.VS) then 
            let lines = GetOutputWindowPaneLines(this.VS)
            AssertListContainsInOrder(lines, 
                                      ["Could not find file "
                                       "Script.fsx"])           
            for line in lines do 
                printfn "%s" line
                AssertNotContains(line,"error MSB") // Microsoft.FSharp.Targets(135,9): error MSB6006: "fsc.exe" exited with code -532462766.

        Assert.True(not(build.BuildSucceeded), "Expected build to fail")                                  
        
        
    /// There was a problem in which synthetic tokens like #load were causing asserts
    [<Fact>]
    member public this.``Fsx.SyntheticTokens``() =     
        Helper.ExhaustivelyScrutinize(
            this.TestRunner,
              ["#light"
               "#r \"\""
               "#reference \"\""
               "#load \"\""
               "#line 52"
               "#nowarn 72"]        
            )
                                 
    /// There was a problem where an unclosed reference picked up the text of the reference on the next line.
    [<Fact>]
    member public this.``Fsx.ShouldBeAbleToReference30Assemblies.Bug2050``() =     
        let code = 
                                    ["#light"
                                     "#r \"System.Core.dll\""
                                     "open System."
                                     ]
        let (_, file) = createSingleFileFsxFromLines code
        MoveCursorToEndOfMarker(file,"open System.") 
        let completions = AutoCompleteAtCursor file
        AssertCompListContains(completions,"Linq")

    /// There was a problem where an unclosed reference picked up the text of the reference on the next line.
    [<Fact>]
    member public this.``Fsx.UnclosedHashReference.Case1``() =     
        Helper.ExhaustivelyScrutinize(
            this.TestRunner,
           ["#light"
            "#reference \"" // Unclosed
            "#reference \"Hello There\""]    
            )
    [<Fact>]
    member public this.``Fsx.UnclosedHashReference.Case2``() =     
        Helper.ExhaustivelyScrutinize(
            this.TestRunner,
          ["#light"
           "#r \"" // Unclosed
           "# \"Hello There\""]                                            
           )
                                     
    /// There was a problem where an unclosed reference picked up the text of the reference on the next line.
    [<Fact>]
    member public this.``Fsx.UnclosedHashLoad``() =     
        Helper.ExhaustivelyScrutinize(
            this.TestRunner, 
            [ "#light"
              "#load \"" // Unclosed
              "#load \"Hello There\""]
            ) 

    [<Fact(Skip = "Bug https://github.com/dotnet/fsharp/issues/17330")>]
    member public this.``TypeProvider.UnitsOfMeasure.SmokeTest1``() =
        let code =
                                    ["open Microsoft.FSharp.Data.UnitSystems.SI.UnitNames"
                                     "let x : System.Nullable<decimal<kilogram / hertz^2>> = N1.T1.MethodWithTypesInvolvingUnitsOfMeasure(1.0<kilogram>)"
                                     "let x2 : int = N1.T1().MethodWithErasedCodeUsingConditional()"
                                     "let x3 : int = N1.T1().MethodWithErasedCodeUsingTypeAs()"
                                     ]
        let refs = 
            [
                PathRelativeToTestAssembly(@"DummyProviderForLanguageServiceTesting.dll")
            ]
        let (_, project, file) = this.CreateSingleFileProject(code, references = refs)
        TakeCoffeeBreak(this.VS)
        AssertNoErrorsOrWarnings(project)

    member public this.TypeProviderDisposalSmokeTest(clearing) =
        use _guard = this.UsingNewVS()
        let providerAssemblyName = PathRelativeToTestAssembly(@"DummyProviderForLanguageServiceTesting.dll")
        let providerAssembly = System.Reflection.Assembly.LoadFrom providerAssemblyName
        Assert.NotNull(providerAssembly)
        let providerCounters = providerAssembly.GetType("DummyProviderForLanguageServiceTesting.GlobalCounters")
        Assert.NotNull(providerCounters)
        let totalCreationsMeth = providerCounters.GetMethod("GetTotalCreations")
        Assert.NotNull(totalCreationsMeth)
        let totalDisposalsMeth = providerCounters.GetMethod("GetTotalDisposals")
        Assert.NotNull(totalDisposalsMeth)
        let checkConfigsMeth = providerCounters.GetMethod("CheckAllConfigsDisposed")
        Assert.NotNull(checkConfigsMeth)

        let providerCounters2 = providerAssembly.GetType("ProviderImplementation.ProvidedTypes.GlobalCountersForInvalidation")
        Assert.NotNull(providerCounters2)
        let totalInvalidationHandlersAddedMeth = providerCounters2.GetMethod("GetInvalidationHandlersAdded")
        Assert.NotNull(totalInvalidationHandlersAddedMeth)
        let totalInvalidationHandlersRemovedMeth = providerCounters2.GetMethod("GetInvalidationHandlersRemoved")
        Assert.NotNull(totalInvalidationHandlersRemovedMeth)

        let totalCreations() = totalCreationsMeth.Invoke(null, [| |]) :?> int
        let totalDisposals() = totalDisposalsMeth.Invoke(null, [| |]) :?> int
        let checkConfigsDisposed() = checkConfigsMeth.Invoke(null, [| |]) |> ignore
        let totalInvalidationHandlersAdded() = totalInvalidationHandlersAddedMeth.Invoke(null, [| |]) :?> int
        let totalInvalidationHandlersRemoved() = totalInvalidationHandlersRemovedMeth.Invoke(null, [| |]) :?> int

         
        let startCreations = totalCreations()
        let startDisposals = totalDisposals()
        let startInvalidationHandlersAdded = totalInvalidationHandlersAdded()
        let startInvalidationHandlersRemoved =  totalInvalidationHandlersRemoved()
        let countCreations() = totalCreations() - startCreations
        let countDisposals() = totalDisposals() - startDisposals
        let countInvalidationHandlersAdded() = totalInvalidationHandlersAdded() - startInvalidationHandlersAdded
        let countInvalidationHandlersRemoved() = totalInvalidationHandlersRemoved() - startInvalidationHandlersRemoved

        Assert.True(startCreations >= startDisposals, "Check0")
        Assert.True(startInvalidationHandlersAdded >= startInvalidationHandlersRemoved, "Check0")
        for i in 1 .. 50 do 
            let solution = this.CreateSolution()
            let project = CreateProject(solution,"testproject" + string (i % 20))    
            this.AddAssemblyReference(project, PathRelativeToTestAssembly(@"DummyProviderForLanguageServiceTesting.dll"))
            let fileName = sprintf "File%d.fs" i
            let file1 = AddFileFromText(project,fileName, ["let x" + string i + " = N1.T1()" ])    
            let file = OpenFile(project,fileName)
            TakeCoffeeBreak(this.VS)
            AssertNoErrorsOrWarnings project   // ...and not an error on the first line.
            MoveCursorToEndOfMarker(file, "N1.T1")

            // do some stuff to get declarations etc.
            let tooltip = GetQuickInfoAtCursor file
            AssertContains(tooltip, "T1")
            ignore (GetF1KeywordAtCursor file)
            let parmInfo = GetParameterInfoAtCursor file

            let file1 = OpenFile(project,fileName)   

            // The disposals should be at least one less 
            let c = countCreations()
            let d = countDisposals()

            // Creations should always be greater or equal to disposals
            Assert.True(c >= d, "Check2, countCreations() >= countDisposals(), iteration " + string i + ", countCreations() = " + string c + ", countDisposals() = " + string d)

            // Creations can run ahead of iterations if the background checker resurrects the builder for a project
            // even after we've moved on from it.
            Assert.True((c >= i), "Check3, countCreations() >= i, iteration " + string i + ", countCreations() = " + string c)

            if not clearing then 
                // By default we hold 3 build incrementalBuilderCache entries and 5 typeCheckInfo entries, so if we're not clearing
                // there should be some roots to project builds still present
                if i >= 3 then 
                    Assert.True(c >= d + 3, "Check4a, c >= countDisposals() + 3, iteration " + string i + ", i = " + string i + ", countDisposals() = " + string d)
                    printfn "Check4a2, i = %d, countInvalidationHandlersRemoved() = %d" i (countInvalidationHandlersRemoved())

            // If we forcefully clear out caches and force a collection, then we can say much stronger things...
            if clearing then 
                ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients(this.VS)
                let c = countCreations()
                let d = countDisposals()

                // Creations should be equal to disposals after a `ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients`
                Assert.True((c = d), "Check4b, countCreations() = countDisposals(), iteration " + string i)
                Assert.True((countInvalidationHandlersAdded() = countInvalidationHandlersRemoved()), "Check4b2, all invalidation handlers removed, iteration " + string i)
        
        let c = countCreations()
        let d = countDisposals()
        Assert.True(c >= 50, "Check5, at end, countCreations() >= 50")

        ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients(this.VS)

        let c = countCreations()
        let d = countDisposals()
        // Creations should be equal to disposals after a `ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients`
        Assert.True((c = d), "Check6b, at end, countCreations() = countDisposals() after explicit clearing")
        Assert.True((countInvalidationHandlersAdded() = countInvalidationHandlersRemoved()), "Check6b2, at end, all invalidation handlers removed after explicit clearing")
        checkConfigsDisposed()

    [<Fact(Skip = "Flaky test, unclear if it is valuable")>]
    member public this.``TypeProvider.Disposal.SmokeTest1``() = this.TypeProviderDisposalSmokeTest(true)

    [<Fact(Skip ="Flaky test, unclear if it is valuable")>]
    member public this.``TypeProvider.Disposal.SmokeTest2``() = this.TypeProviderDisposalSmokeTest(false)


// Context project system
type UsingProjectSystem() = 
    inherit UsingMSBuild(VsOpts = LanguageServiceExtension.ProjectSystemTestFlavour)

