Enhance testing; add more generators; add testing for approxEqual
authorvaron <varon@users.noreply.github.com>
Sun, 19 Mar 2017 13:35:46 +0000 (15:35 +0200)
committervaron <varon@users.noreply.github.com>
Sun, 19 Mar 2017 13:35:46 +0000 (15:35 +0200)
tests/OpenTK.Tests/Assertions.fs [new file with mode: 0644]
tests/OpenTK.Tests/Generators.fs [new file with mode: 0644]
tests/OpenTK.Tests/MathHelper.fs [new file with mode: 0644]
tests/OpenTK.Tests/OpenTK.Tests.fsproj
tests/OpenTK.Tests/Vectors.fs

diff --git a/tests/OpenTK.Tests/Assertions.fs b/tests/OpenTK.Tests/Assertions.fs
new file mode 100644 (file)
index 0000000..93baa61
--- /dev/null
@@ -0,0 +1,31 @@
+namespace OpenTK.Tests
+
+open Xunit
+open FsCheck
+open FsCheck.Xunit
+open System
+open OpenTK
+
+[<AutoOpen>]
+module private AssertHelpers = 
+    [<Literal>]
+    let private BitAccuracy = 4
+    
+    let approxEq a b = MathHelper.ApproximatelyEqual(a,b,BitAccuracy)
+
+/// We use a full type here instead of a module, as the overloading semantics are more suitable for our desired goal.
+[<Sealed>]
+type internal Assert = 
+    
+    static member ApproximatelyEqual(a : Vector2,b : Vector2) = 
+        if not <| approxEq a.X b.X && approxEq a.Y b.Y then raise <| new Xunit.Sdk.EqualException(a,b)
+    
+    static member ApproximatelyEqual(a : Vector3,b : Vector3) = 
+        if not <| approxEq a.X b.X && approxEq a.Y b.Y && approxEq a.Z b.Z then raise <| new Xunit.Sdk.EqualException(a,b)
+    
+    static member ApproximatelyEqual(a : Vector4,b : Vector4) = 
+        if not <| approxEq a.X b.X && approxEq a.Y b.Y && approxEq a.Z b.Z && approxEq a.W b.W then 
+            raise <| new Xunit.Sdk.EqualException(a,b)
+    
+    static member ApproximatelyEqual(a : float32,b : float32) = 
+        if not <| approxEq a b then raise <| new Xunit.Sdk.EqualException(a,b)
diff --git a/tests/OpenTK.Tests/Generators.fs b/tests/OpenTK.Tests/Generators.fs
new file mode 100644 (file)
index 0000000..a57424f
--- /dev/null
@@ -0,0 +1,76 @@
+namespace OpenTK.Tests
+
+open Xunit
+open FsCheck
+open FsCheck.Xunit
+open System
+open OpenTK
+
+[<AutoOpen>]
+module private Generators = 
+    let private isValidFloat f = not (Single.IsNaN f || Single.IsInfinity f || Single.IsInfinity (f * f) || f = Single.MinValue || f = Single.MaxValue )
+    let private isValidDouble d = not (Double.IsNaN d || Double.IsInfinity d || Double.IsInfinity (d * d)|| d = Double.MinValue || d = Double.MaxValue)
+    let singleArb = Arb.Default.Float32() |> Arb.toGen |> Gen.filter isValidFloat
+    let single = singleArb |> Arb.fromGen
+    
+    let double = 
+        Arb.Default.Float() |> Arb.toGen
+        |> Gen.filter isValidDouble
+        |> Arb.fromGen
+    
+    let vec2 = 
+        singleArb
+        |> Gen.two
+        |> Gen.map Vector2
+        |> Arb.fromGen
+    
+    let vec3 = 
+        singleArb
+        |> Gen.three
+        |> Gen.map Vector3
+        |> Arb.fromGen
+    
+    let vec4 = 
+        singleArb
+        |> Gen.four
+        |> Gen.map Vector4
+        |> Arb.fromGen
+    
+    let quat = 
+        singleArb
+        |> Gen.four
+        |> Gen.map Quaternion
+        |> Arb.fromGen
+    
+    let mat2 = 
+        singleArb
+        |> Gen.four
+        |> Gen.map Matrix2
+        |> Arb.fromGen
+    
+    let mat3 = 
+        vec3
+        |> Arb.toGen
+        |> Gen.three
+        |> Gen.map Matrix3
+        |> Arb.fromGen
+    
+    let mat4 = 
+        vec4
+        |> Arb.toGen
+        |> Gen.four
+        |> Gen.map Matrix4
+        |> Arb.fromGen
+
+type OpenTKGen = 
+    static member Single() = single
+    static member float32() = single
+    static member Double() = double
+    static member float() = double
+    static member Vector2() = vec2
+    static member Vector3() = vec3
+    static member Vector4() = vec4
+    static member Quaternion() = quat
+    static member Matrix2() = mat2
+    static member Matrix3() = mat3
+    static member Matrix4() = mat4
diff --git a/tests/OpenTK.Tests/MathHelper.fs b/tests/OpenTK.Tests/MathHelper.fs
new file mode 100644 (file)
index 0000000..7c37a25
--- /dev/null
@@ -0,0 +1,55 @@
+namespace OpenTK.Tests
+
+open Xunit
+open FsCheck
+open FsCheck.Xunit
+open System
+open OpenTK
+
+[<Properties(Arbitrary = [| typeof<OpenTKGen> |])>]
+module MathHelper = 
+    /// This test ensures that approximately equal can never get it 'wrong' about the values.
+    [<Property>]
+    let ``ApproximatelyEqual is never incorrect`` (a : float32,b : float32,bits : int32) = 
+        let clamped = max 0 (min bits 24)
+        let areApproxEqual = MathHelper.ApproximatelyEqual(a,b,clamped)
+        let areExactlyEqual = a = b
+        let isWrong = areExactlyEqual && not areApproxEqual
+        Assert.False(isWrong)
+    
+    [<Property>]
+    let ``ApproximatelyEqual can return true if some values are not exactly equal`` (a : float32,b : float32,bits : int32) = 
+        let clamped = max 0 (min bits 24)
+        let areApproxEqual = MathHelper.ApproximatelyEqual(a,b,clamped)
+        let areExactlyEqual = a = b
+        let isWrong = areExactlyEqual && not areApproxEqual
+        let p = new PropertyAttribute()
+        Assert.False(isWrong)
+    
+    [<Fact>]
+    let ``ApproximatelyEqual correctly approximates equality``() = 
+        let a = 0.000000001f
+        let b = 0.0000000010000001f
+        Assert.NotEqual(a,b)
+        [ 1..24 ] |> List.iter (fun i -> Assert.True(MathHelper.ApproximatelyEqual(a,b,i)))
+    
+    [<Fact>]
+    let ``ApproximatelyEqual reports very different values as non-equal even with high bit count``() = 
+        let a = 2.0f
+        let b = 1.0f
+        Assert.NotEqual(a,b)
+        Assert.False(MathHelper.ApproximatelyEqual(a,b,10))
+    
+    [<Fact>]
+    let ``ApproximatelyEqual works with single zero value``() = 
+        let a = 1.0f
+        let b = 0.0f
+        Assert.NotEqual(a,b)
+        Assert.False(MathHelper.ApproximatelyEqual(a,b,0))
+    
+    [<Fact>]
+    let ``ApproximatelyEqual works with both zero values``() = 
+        let a = 0.0f
+        let b = 0.0f
+        Assert.Equal(a,b)
+        Assert.True(MathHelper.ApproximatelyEqual(a,b,0))
index 46190f2..c57ae0c 100644 (file)
   <ItemGroup>
     <Compile Include="AssemblyInfo.fs" />
     <None Include="paket.references" />
+    <Compile Include="Assertions.fs" />
+    <Compile Include="Generators.fs" />
+    <Compile Include="MathHelper.fs" />
     <Compile Include="Vectors.fs" />
     <Content Include="App.config" />
   </ItemGroup>
index 90ba1b1..13e6a75 100644 (file)
@@ -6,38 +6,8 @@ open FsCheck.Xunit
 open System
 open OpenTK
 
-[<RequireQualifiedAccess>]
-module internal Generators = 
-    let private isValidFloat f = not (Single.IsNaN f || Single.IsInfinity f)
-    
-    let Vec2 = 
-        Arb.generate<float32>
-        |> Gen.filter isValidFloat
-        |> Gen.two
-        |> Gen.map Vector2
-        |> Arb.fromGen
-    
-    let Vec3 = 
-        Arb.generate<float32>
-        |> Gen.filter isValidFloat
-        |> Gen.three
-        |> Gen.map Vector3
-        |> Arb.fromGen
-    
-    let Vec4 = 
-        Arb.generate<float32>
-        |> Gen.filter isValidFloat
-        |> Gen.four
-        |> Gen.map Vector4
-        |> Arb.fromGen
-
-type VectorGen = 
-    static member Vector2() = Generators.Vec2
-    static member Vector3() = Generators.Vec3
-    static member Vector4() = Generators.Vec4
-
 module Vector2 = 
-    [<Properties(Arbitrary = [| typeof<VectorGen> |])>]
+    [<Properties(Arbitrary = [| typeof<OpenTKGen> |])>]
     module ``Simple Properties`` = 
         //
         [<Property>]
@@ -50,28 +20,28 @@ module Vector2 =
             //
             Assert.True(a.Length >= 0.0f)
     
-    [<Properties(Arbitrary = [| typeof<VectorGen> |])>]
+    [<Properties(Arbitrary = [| typeof<OpenTKGen> |])>]
     module Addition = 
         //
         [<Property>]
         let ``Vector addition is the same as component addition`` (a : Vector2,b : Vector2) = 
             let c = a + b
-            Assert.Equal(a.X + b.X,c.X)
-            Assert.Equal(a.Y + b.Y,c.Y)
+            Assert.ApproximatelyEqual(a.X + b.X,c.X)
+            Assert.ApproximatelyEqual(a.Y + b.Y,c.Y)
         
         [<Property>]
         let ``Vector addition is commutative`` (a : Vector2,b : Vector2) = 
             let c = a + b
             let c2 = b + a
-            Assert.Equal(c,c2)
+            Assert.ApproximatelyEqual(c,c2)
         
         [<Property>]
         let ``Vector addition is associative`` (a : Vector2,b : Vector2,c : Vector2) = 
             let r1 = (a + b) + c
             let r2 = a + (b + c)
-            Assert.Equal(r1,r2)
+            Assert.ApproximatelyEqual(r1,r2)
     
-    [<Properties(Arbitrary = [| typeof<VectorGen> |])>]
+    [<Properties(Arbitrary = [| typeof<OpenTKGen> |])>]
     module Multiplication = 
         //
         [<Property>]
@@ -92,7 +62,7 @@ module Vector2 =
             Assert.Equal(a.X * f,r.X)
             Assert.Equal(a.Y * f,r.Y)
     
-    [<Properties(Arbitrary = [| typeof<VectorGen> |])>]
+    [<Properties(Arbitrary = [| typeof<OpenTKGen> |])>]
     module Subtraction = 
         //
         [<Property>]
@@ -101,12 +71,11 @@ module Vector2 =
             Assert.Equal(a.X - b.X,c.X)
             Assert.Equal(a.Y - b.Y,c.Y)
     
-    [<Properties(Arbitrary = [| typeof<VectorGen> |])>]
+    [<Properties(Arbitrary = [| typeof<OpenTKGen> |])>]
     module Division = 
         //
         [<Property>]
         let ``Vector-float division is the same as component-float division`` (a : Vector2,f : float32) = 
-            if f <> 0.0f then 
-                let r = a / f
-                Assert.Equal(a.X / f,r.X)
-                Assert.Equal(a.Y / f,r.Y)
+            let r = a / f
+            Assert.ApproximatelyEqual(a.X / f,r.X)
+            Assert.ApproximatelyEqual(a.Y / f,r.Y)