未验证 提交 3f011ec9 编写于 作者: V Vlad Zarytovskii 提交者: GitHub

Simplified compiler test framework (#9721)

* [WIP] Simplified compiler API for testing

* [WIP] Support of references, for F#, C# projects. Added more asserts.

* Added more examples + ignoreWarnings support

* Added typecheck + baseline test helpers.

* Added running functionality

* Moved a bunch of compiler tests to a new suite

* Changed error message

* [WIP] Added CSharp compilation + referencing

* Added some simple interop tests

* [WIP] Process MetadataReference recursively

* [WIP] Added netcorapp31 support for tests

* [WIP] Termporary revert to using netcoreapp30 in the tests (instead of netcoreapp31)

* Added types to errors/warnings as oppose to just having tuples

* Fixed tests to fit newer API

* Revert libraries upgrade

* Revert change netcoreapp31 -> netcoreapp30

* Update tests/FSharp.Test.Utilities/Compiler.fs
Co-authored-by: NPhillip Carter <pcarter@fastmail.com>

* Addressed some PR comments

* Fixed missing styling inconsistensies

* Addressed feedback: recursive module instead of forward type declarations + fixed options composition.

* Changed Option.isSome -> defaultArg
Co-authored-by: NPhillip Carter <pcarter@fastmail.com>
上级 d82a0ebf
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace FSharp.Compiler.UnitTests
namespace FSharp.Compiler.ConstraintSolver.ComponentTests
open NUnit.Framework
open FSharp.Test.Utilities
open FSharp.Compiler.SourceCodeServices
open Xunit
open FSharp.Test.Utilities.Compiler
[<TestFixture>]
module MemberConstraints =
[<Test>]
let ``we can overload operators on a type and not add all the extra jazz such as inlining and the ^ operator.``() =
CompilerAssert.CompileExeAndRun
"""
type Foo(x : int) =
[<Fact>]
let ``Invalid member constraint with ErrorRanges``() = // Regression test for FSharp1.0:2262
FSharp """
let inline length (x: ^a) : int = (^a : (member Length : int with get, set) (x, ()))
"""
|> withOptions ["--test:ErrorRanges"]
|> typecheck
|> shouldFail
|> withSingleDiagnostic (Error 697, Line 2, Col 43, Line 2, Col 76, "Invalid constraint")
[<Fact>]
let ``we can overload operators on a type and not add all the extra jazz such as inlining and the ^ operator.``() =
FSharp """
type Foo(x : int) =
member this.Val = x
static member (-->) ((src : Foo), (target : Foo)) = new Foo(src.Val + target.Val)
static member (-->) ((src : Foo), (target : int)) = new Foo(src.Val + target)
static member (+) ((src : Foo), (target : Foo)) = new Foo(src.Val + target.Val)
static member (+) ((src : Foo), (target : int)) = new Foo(src.Val + target)
let x = Foo(3) --> 4
let y = Foo(3) --> Foo(4)
let x2 = Foo(3) + 4
......@@ -32,16 +40,8 @@ elif y.Val <> 7 then failwith "y.Val <> 7"
elif x2.Val <> 7 then failwith "x2.Val <> 7"
elif y2.Val <> 7 then failwith "x.Val <> 7"
else ()
"""
[<Test>]
let ``Invalid member constraint with ErrorRanges``() = // Regression test for FSharp1.0:2262
CompilerAssert.TypeCheckSingleErrorWithOptions
[| "--test:ErrorRanges" |]
"""
let inline length (x: ^a) : int = (^a : (member Length : int with get, set) (x, ()))
"""
FSharpErrorSeverity.Error
697
(2, 42, 2, 75)
"Invalid constraint"
"""
|> asExe
|> compile
|> run
// OR |> compileAsExeAndRun
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace FSharp.Compiler.UnitTests
namespace FSharp.Compiler.ConstraintSolver.ComponentTests
open NUnit.Framework
open FSharp.Test.Utilities
open FSharp.Compiler.SourceCodeServices
open Xunit
open FSharp.Test.Utilities.Compiler
[<TestFixture>]
module PrimitiveConstraints =
[<Test>]
[<Fact>]
/// Title: Type checking oddity
///
/// This suggestion was resolved as by design,
/// so the test makes sure, we're emitting error message about 'not being a valid object construction expression'
let ``Invalid object constructor`` () = // Regression test for FSharp1.0:4189
baseline
((__SOURCE_DIRECTORY__ ++ "../testables/"), "typecheck/constructors/neg_invalid_constructor.fs")
|> withOptions ["--test:ErrorRanges"]
|> typecheck
[<Fact>]
let ``Test primitive : constraints``() =
CompilerAssert.CompileExeAndRun
"""
FSharp"""
#light
type Foo(x : int) =
......@@ -33,12 +42,12 @@ let b = new Bar(256)
if test1 f <> 128 then failwith "test1 f <> 128"
elif test2 b <> (-1, 256) then failwith "test2 b <> (-1, 256)"
else ()
"""
"""
|> compileExeAndRun
[<Test>]
[<Fact>]
let ``Test primitive :> constraints``() =
CompilerAssert.CompileExeAndRun
"""
FSharp"""
#light
type Foo(x : int) =
member this.Value = x
......@@ -64,12 +73,12 @@ if test f <> (128, "Foo") then failwith "test f <> (128, 'Foo')"
elif test b <> (-1, "Bar") then failwith "test b <> (-1, 'Bar')"
elif test r <> (10, "Ram") then failwith "test r <> (10, 'Ram')"
else ()
"""
"""
|> compileExeAndRun
[<Test>]
[<Fact>]
let ``Test primitive : null constraint``() =
CompilerAssert.CompileExeAndRun
"""
FSharp"""
let inline isNull<'a when 'a : null> (x : 'a) =
match x with
| null -> "is null"
......@@ -84,12 +93,5 @@ let runTest =
with _ -> reraise()
runTest
"""
[<Test>]
/// Title: Type checking oddity
///
/// This suggestion was resolved as by design,
/// so the test makes sure, we're emitting error message about 'not being a valid object construction expression'
let ``Invalid object constructor``() = // Regression test for FSharp1.0:4189
CompilerAssert.TypeCheckWithErrorsAndOptionsAgainstBaseLine [| "--test:ErrorRanges" |] (__SOURCE_DIRECTORY__ ++ "../../") "typecheck/constructors/neg_invalid_constructor.fs"
"""
|> compileExeAndRun
......@@ -4,46 +4,40 @@ namespace FSharp.Compiler.ErrorMessages.ComponentTests
open Xunit
open FSharp.Test.Utilities
open FSharp.Test.Utilities.Compiler
open FSharp.Test.Utilities.Utilities
open FSharp.Compiler.SourceCodeServices
module ``Confusing Type Name`` =
[<Fact>]
let ``Checks expected types with multiple references``() =
let csLibAB = """
let ``Expected types with multiple references`` () =
let csLibA =
CSharp """
public class A { }
public class B<T> { }
"""
let csLibACmpl =
CompilationUtil.CreateCSharpCompilation(csLibAB, CSharpLanguageVersion.CSharp8, TargetFramework.NetCoreApp30, name = "libA")
|> CompilationReference.Create
""" |> withName "libA"
let csLibBCmpl =
CompilationUtil.CreateCSharpCompilation(csLibAB, CSharpLanguageVersion.CSharp8, TargetFramework.NetCoreApp30, name = "libB")
|> CompilationReference.Create
let csLibB =
csLibA |> withName "libB"
let fsLibC = """
let fsLibC =
FSharp """
module AMaker
let makeA () : A = A()
let makeB () = B<_>()
"""
""" |> withName "libC" |> withReferences [csLibA]
let fsLibD = """
let fsLibD =
FSharp """
module OtherAMaker
let makeOtherA () : A = A()
let makeOtherB () = B<_>()
"""
let fsLibCCmpl =
Compilation.Create(fsLibC, Fs, Library, cmplRefs = [csLibACmpl], name = "libC")
|> CompilationReference.CreateFSharp
""" |> withName "libD" |> withReferences [csLibB]
let fsLibDCmpl =
Compilation.Create(fsLibD, Fs, Library, cmplRefs = [csLibBCmpl], name = "libD")
|> CompilationReference.CreateFSharp
let app = """
let app =
FSharp """
module ConfusingTypeName
let a = AMaker.makeA()
let otherA = OtherAMaker.makeOtherA()
......@@ -54,14 +48,15 @@ let b = AMaker.makeB<int>()
let otherB = OtherAMaker.makeOtherB<int>()
printfn "%A %A" (b.GetType().AssemblyQualifiedName) (otherB.GetType().AssemblyQualifiedName)
printfn "%A" (b = otherB)
"""
let appCmpl =
Compilation.Create(app, Fs, Library, cmplRefs = [csLibACmpl; csLibBCmpl; fsLibCCmpl; fsLibDCmpl])
CompilerAssert.CompileWithErrors(
appCmpl,
[|
(FSharpErrorSeverity.Error, 1, (6, 19, 6, 25), ("This expression was expected to have type\n 'A (libA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)' \nbut here has type\n 'A (libB, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)' "))
(FSharpErrorSeverity.Error, 1, (11, 19, 11, 25), ("This expression was expected to have type\n 'B<Microsoft.FSharp.Core.int> (libA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)' \nbut here has type\n 'B<Microsoft.FSharp.Core.int> (libB, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)' "))
|], true)
""" |> withReferences [csLibA; csLibB; fsLibC; fsLibD]
app
|> compile
|> shouldFail
|> withDiagnostics [
(Warning 686, Line 8, Col 9, Line 8, Col 21, "The method or function 'makeB' should not be given explicit type argument(s) because it does not declare its type parameters explicitly")
(Warning 686, Line 9, Col 14, Line 9, Col 36, "The method or function 'makeOtherB' should not be given explicit type argument(s) because it does not declare its type parameters explicitly")
(Error 1, Line 6, Col 19, Line 6, Col 25, "This expression was expected to have type\n 'A (libA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)' \nbut here has type\n 'A (libB, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)' ")
(Error 1, Line 11, Col 19, Line 11, Col 25, "This expression was expected to have type\n 'B<Microsoft.FSharp.Core.int> (libA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)' \nbut here has type\n 'B<Microsoft.FSharp.Core.int> (libB, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)' ")
]
......@@ -37,6 +37,10 @@
<Compile Include="ErrorMessages\WarnExpressionTests.fs" />
<Compile Include="ErrorMessages\WrongSyntaxInForLoop.fs" />
<Compile Include="ErrorMessages\ConfusingTypeName.fs" />
<Compile Include="Language\CompilerDirectiveTests.fs" />
<Compile Include="ConstraintSolver\PrimitiveConstraints.fs" />
<Compile Include="ConstraintSolver\MemberConstraints.fs" />
<Compile Include="Interop/SimpleInteropTests.fs" />
</ItemGroup>
<ItemGroup>
......
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace FSharp.Compiler.ErrorMessages.ComponentTests
open Xunit
open FSharp.Test.Utilities
open FSharp.Test.Utilities.Compiler
module ``C# <-> F# basic interop`` =
[<Fact>]
let ``Instantiate C# type from F#`` () =
let CSLib =
CSharp """
public class A { }
""" |> withName "CSLib"
let FSLib =
FSharp """
module AMaker
let makeA () : A = A()
""" |> withName "FSLib" |> withReferences [CSLib]
let app =
FSharp """
module ReferenceCSfromFS
let a = AMaker.makeA()
""" |> withReferences [CSLib; FSLib]
app
|> compile
|> shouldSucceed
[<Fact(Skip="TODO: This is broken RN, since netcoreapp30 is used for C# and 3.1 for F#, should be fixed as part of https://github.com/dotnet/fsharp/issues/9740")>]
let ``Instantiate F# type from C#`` () =
let FSLib =
FSharp """
namespace Interop.FS
type Bicycle(manufacturer: string) =
member this.Manufactirer = manufacturer
""" |> withName "FSLib"
let app =
CSharp """
using Interop.FS;
public class BicycleShop {
public Bicycle[] cycles;
}
""" |> withReferences [FSLib]
app
|> compile
|> shouldSucceed
[<Fact>]
let ``Instantiate F# type from C# fails without import`` () =
let FSLib =
FSharp """
namespace Interop.FS
type Bicycle(manufacturer: string) =
member this.Manufactirer = manufacturer
""" |> withName "FSLib"
let app =
CSharp """
public class BicycleShop {
public Bicycle[] cycles;
}
""" |> withReferences [FSLib]
app
|> compile
|> shouldFail
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace FSharp.Compiler.Language.ComponentTests
open Xunit
open FSharp.Test.Utilities
open FSharp.Test.Utilities.Compiler
open FSharp.Compiler.SourceCodeServices
module ``Test Compiler Directives`` =
[<Fact>]
let ``r# "" is invalid`` () =
Fsx"""
#r ""
""" |> ignoreWarnings
|> compile
|> shouldSucceed
|> withSingleDiagnostic (Warning 213, Line 2, Col 1, Line 2, Col 6, "'' is not a valid assembly name")
[<Fact>]
let ``#r " " is invalid`` () =
Fsx"""
#r " "
""" |> compile
|> shouldFail
|> withSingleDiagnostic (Warning 213, Line 2, Col 1, Line 2, Col 10, "'' is not a valid assembly name")
typecheck/constructors/neg_invalid_constructor.fs (3,29)-(3,56) typecheck error A unique overload for method 'ImmutableStack`1' could not be determined based on type information prior to this program point. A type annotation may be needed.
Known type of argument: 'a list
Candidates:
- new : col:'b -> ImmutableStack<'a>
- private new : items:'a list -> ImmutableStack<'a>
typecheck/constructors/neg_invalid_constructor.fs (4,93)-(4,111) typecheck error A unique overload for method 'ImmutableStack`1' could not be determined based on type information prior to this program point. A type annotation may be needed.
Known type of argument: 'a list
Candidates:
- new : col:'b -> ImmutableStack<'a>
- private new : items:'a list -> ImmutableStack<'a>
typecheck/constructors/neg_invalid_constructor.fs (7,30)-(7,60) typecheck error A unique overload for method 'ImmutableStack`1' could not be determined based on type information prior to this program point. A type annotation may be needed.
Known type of argument: 'a list
Candidates:
- new : col:'b -> ImmutableStack<'a> when 'b :> seq<'c>
- private new : items:'a list -> ImmutableStack<'a>
typecheck/constructors/neg_invalid_constructor.fs (7,30)-(7,60) typecheck error This is not a valid object construction expression. Explicit object constructors must either call an alternate constructor or initialize all fields of the object and specify a call to a super class constructor.
\ No newline at end of file
type ImmutableStack<'a> private(items: 'a list) =
member this.Push item = ImmutableStack(item::items)
member this.Pop = match items with | [] -> failwith "No elements in stack" | x::xs -> x,ImmutableStack(xs)
// Notice type annotation is commented out, which results in an error
new(col (*: seq<'a>*)) = ImmutableStack(List.ofSeq col)
......@@ -4,6 +4,9 @@ module Assert =
open FluentAssertions
open System.Collections
let inline shouldBeEqualWith (expected : ^T) (message: string) (actual: ^U) =
actual.Should().BeEquivalentTo(expected, message) |> ignore
let inline shouldBeEquivalentTo (expected : ^T) (actual : ^U) =
actual.Should().BeEquivalentTo(expected, "") |> ignore
......
此差异已折叠。
......@@ -362,6 +362,14 @@ let main argv = 0"""
disposals
|> Seq.iter (fun x -> x.Dispose())
// NOTE: This function will not clean up all the compiled projects after itself.
// The reason behind is so we can compose verification of test runs easier.
// TODO: We must not rely on the filesystem when compiling
static let rec returnCompilation (cmpl: Compilation) =
let compileDirectory = Path.Combine(Path.GetTempPath(), "CompilerAssert", Path.GetRandomFileName())
Directory.CreateDirectory(compileDirectory) |> ignore
compileCompilationAux compileDirectory (ResizeArray()) false cmpl
static member CompileWithErrors(cmpl: Compilation, expectedErrors, ?ignoreWarnings) =
let ignoreWarnings = defaultArg ignoreWarnings false
lock gate (fun () ->
......@@ -371,6 +379,9 @@ let main argv = 0"""
static member Compile(cmpl: Compilation, ?ignoreWarnings) =
CompilerAssert.CompileWithErrors(cmpl, [||], defaultArg ignoreWarnings false)
static member CompileRaw(cmpl: Compilation) =
lock gate (fun () -> returnCompilation cmpl)
static member Execute(cmpl: Compilation, ?ignoreWarnings, ?beforeExecute, ?newProcess, ?onOutput) =
let ignoreWarnings = defaultArg ignoreWarnings false
let beforeExecute = defaultArg beforeExecute (fun _ _ -> ())
......@@ -481,6 +492,27 @@ let main argv = 0"""
Assert.AreEqual(errorsExpectedBaseLine.Replace("\r\n","\n"), errorsActual.Replace("\r\n","\n"))
static member TypeCheckWithOptions options (source: string) =
lock gate <| fun () ->
let errors =
let parseResults, fileAnswer =
checker.ParseAndCheckFileInProject(
"test.fs",
0,
SourceText.ofString source,
{ defaultProjectOptions with OtherOptions = Array.append options defaultProjectOptions.OtherOptions})
|> Async.RunSynchronously
if parseResults.Errors.Length > 0 then
parseResults.Errors
else
match fileAnswer with
| FSharpCheckFileAnswer.Aborted _ -> Assert.Fail("Type Checker Aborted"); [| |]
| FSharpCheckFileAnswer.Succeeded(typeCheckResults) -> typeCheckResults.Errors
errors
static member TypeCheckWithErrorsAndOptionsAndAdjust options libAdjust (source: string) expectedTypeErrors =
lock gate <| fun () ->
let errors =
......@@ -502,6 +534,7 @@ let main argv = 0"""
assertErrors libAdjust false errors expectedTypeErrors
static member TypeCheckWithErrorsAndOptions options (source: string) expectedTypeErrors =
CompilerAssert.TypeCheckWithErrorsAndOptionsAndAdjust options 0 (source: string) expectedTypeErrors
......@@ -587,6 +620,9 @@ let main argv = 0"""
static member RunScript source expectedErrorMessages =
CompilerAssert.RunScriptWithOptions [||] source expectedErrorMessages
static member Run (exe: string) =
executeBuiltApp exe []
static member ParseWithErrors (source: string) expectedParseErrors =
let sourceFileName = "test.fs"
let parsingOptions = { FSharpParsingOptions.Default with SourceFiles = [| sourceFileName |] }
......
......@@ -22,6 +22,7 @@
<Compile Include="Utilities.fs" />
<Compile Include="CompilerAssert.fs" />
<Compile Include="Assert.fs" />
<Compile Include="Compiler.fs" />
</ItemGroup>
<ItemGroup>
......@@ -34,7 +35,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Net.Compilers" Version="$(MicrosoftNetCompilersVersion)" />
<PackageReference Include="Microsoft.Net.Compilers.Toolset" Version="$(MicrosoftNetCompilersToolsetVersion)" />
<PackageReference Include="Microsoft.NETCore.ILDAsm" Version="$(MicrosoftNETCoreILDAsmVersion)" />
<PackageReference Include="Microsoft.NETCore.ILAsm" Version="$(MicrosoftNETCoreILAsmVersion)" />
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersion)" />
......
......@@ -4,6 +4,7 @@ namespace FSharp.Test.Utilities
open System
open System.IO
open System.Reflection
open System.Collections.Immutable
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CSharp
......@@ -20,7 +21,6 @@ module Utilities =
| NetCoreApp30
module private TestReferences =
[<RequireQualifiedAccess>]
module NetStandard20 =
let netStandard = lazy AssemblyMetadata.CreateFromImage(TestResources.NetFX.netstandard20.netstandard).GetReference(display = "netstandard.dll (netstandard 2.0 ref)")
......@@ -38,8 +38,9 @@ module Utilities =
let systemDynamicRuntimeRef = lazy AssemblyMetadata.CreateFromImage(TestResources.NetFX.netcoreapp30.System_Dynamic_Runtime).GetReference(display = "System.Dynamic.Runtime.dll (netcoreapp 3.0 ref)")
let systemConsoleRef = lazy AssemblyMetadata.CreateFromImage(TestResources.NetFX.netcoreapp30.System_Console).GetReference(display = "System.Console.dll (netcoreapp 3.0 ref)")
[<RequireQualifiedAccess>]
module private TargetFrameworkUtil =
module internal TargetFrameworkUtil =
open TestReferences
......@@ -60,8 +61,6 @@ module Utilities =
| None = 0x0
| InternalsVisibleTo = 0x1
// TODO: this and Compilation.Compile needs to be merged for sake of consistency.
// TODO: After merging, add new type of FSharp compilation.
[<RequireQualifiedAccess>]
type TestCompilation =
| CSharp of CSharpCompilation
......
......@@ -37,8 +37,6 @@
<Compile Include="Compiler\Conformance\BasicGrammarElements\DecimalConstants.fs" />
<Compile Include="Compiler\Conformance\BasicGrammarElements\IntegerConstants.fs" />
<Compile Include="Compiler\Conformance\Properties\ILMemberAccessTests.fs" />
<Compile Include="Compiler\ConstraintSolver\PrimitiveConstraints.fs" />
<Compile Include="Compiler\ConstraintSolver\MemberConstraints.fs" />
<Compile Include="Compiler\Warnings\AssignmentWarningTests.fs" />
<Compile Include="Compiler\Warnings\ExperimentalAttributeTests.fs" />
<Compile Include="Compiler\Warnings\PatternMatchingWarningTests.fs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册