packages feed

HaGL-0.1.0.0: tests/Test.hs

{-# LANGUAGE NumericUnderscores #-}

import Prelude hiding (all, min, max, length, floor)
import Test.HUnit
import Control.Monad (when)
import Control.Exception (Exception, try)
import System.Exit
import System.Directory (removeFile, createDirectoryIfMissing)
import qualified Data.ByteString as BS
import qualified Data.List as List
import qualified Graphics.UI.GLUT as GLUT

import Graphics.HaGL hiding (not, abs, sin, cos, sqrt)
import Graphics.HaGL.Internal


-- Test setup

data ExprTest d where
    ExprTest :: String -> GLExpr d Bool -> ExprTest d

data ExprExceptionTest =
    ExprExceptionTest String GLExprException (GLExpr FragmentDomain Bool)

data ObjTest =
    ObjTest String [GLObj]

data ObjExceptionTest where
    ObjExceptionTest :: (Exception e, Eq e) => String -> e -> [GLObj] -> ObjExceptionTest

-- verify that 'expr' evaluates to True
mkHostTest :: ExprTest HostDomain -> Test
mkHostTest (ExprTest label expr) = 
    TestLabel (label ++ "_host") $ TestCase $ do
        let ioev = error "Host tests do not support evaluation of I/O GLExprs"
        res <- hostEval ioev expr
        assertBool ("Failed to evaluate to true on host: " ++ label) res

-- verify that setting color to 'expr' produces a white image
mkShaderTest :: ExprTest FragmentDomain -> Test
mkShaderTest (ExprTest label expr) = 
    mkObjTest $ ObjTest label $ return $ fromImage $ \_ -> cast expr .# 1

-- verify that trying to set color to 'expr' throws the expected exception
mkShaderExceptionTest :: ExprExceptionTest -> Test
mkShaderExceptionTest (ExprExceptionTest label ex expr) =
    mkObjExceptionTest $ ObjExceptionTest label ex $ return $ fromImage $ \_ -> cast expr .# 1

-- verify that drawing the given objects produces a white image
mkObjTest :: ObjTest -> Test
mkObjTest (ObjTest label objs) = 
    TestLabel (label ++ "_shader") $ TestCase $
        runObjs False label objs >>=
            assertBool ("Failed to obtain a true value in shader: " ++ label)

-- verify that drawing the given objects throws the expected exception
mkObjExceptionTest :: ObjExceptionTest -> Test
mkObjExceptionTest (ObjExceptionTest label ex objs) =
    TestLabel label $ TestCase $ do
        res <- try $ runObjs False label objs
        case res of
            Left ex' | ex' == ex -> return ()
            _ -> assertFailure $ "Expected exception: " ++ show ex

fromImage :: (FragExpr (Vec 2 Float) -> FragExpr (Vec 4 Float)) -> GLObj
fromImage im = triangleStrip { position = pos $- vec2 0 1, color = color } where
    pos = vert 
        [vec2 (-1) (-1), 
         vec2 (-1) 1, 
         vec2 1 (-1), 
         vec2 1 1] 
    color = im (frag pos)

runObjs :: Bool -> String -> [GLObj] -> IO Bool
runObjs alwaysSave label objs = do
    mapM_ (dumpObj label) (zip [0..] objs)
    let captureDst = "output/test/test_" ++ label
    -- TODO: use the same GLUT window instead of creating a new one every time
    GLUT.exit -- make sure GLUT has cleaned up, if a previous test errored
    -- TODO: calling drawGlut directly is inefficient 
    -- as it generates shader code a second time
    drawGlutCustom (defaultGlutOptions { 
        runMode = GlutCaptureAndExit captureDst }) objs
    dat <- BS.readFile (captureDst ++ ".ppm")
    let success = BS.all (== 0xff) . BS.drop 16 $ dat
    when (not alwaysSave && success) $ removeFile (captureDst ++ ".ppm")
    return success

dumpObj label (i, obj) = do
    let astDump = printGLExpr (position obj) ++ "\n" ++ printGLExpr (color obj)
        glsl = dumpGlsl obj
        objLabel = label ++ if i == 0 then "" else show i
    writeFile ("output/test/test_" ++ objLabel ++ ".dump") astDump
    writeFile ("output/test/test_" ++ objLabel ++ ".glsl") glsl

-- TODO: make these definitions part of GLType & use a smarter error estimate
almostEqual x y = abs (x - y) .<= 1e-5
almostVecEqual x y = all $ lessThanEqual (abs (x - y)) 1e-5
almostMatPx2Equal m1 m2 = 
    almostVecEqual (col0 m1) (col0 m2) .&&
    almostVecEqual (col1 m1) (col1 m2)
almostMatPx3Equal m1 m2 =
    almostVecEqual (col0 m1) (col0 m2) .&&
    almostVecEqual (col1 m1) (col1 m2) .&&
    almostVecEqual (col2 m1) (col2 m2)
almostMatPx4Equal m1 m2 =
    almostVecEqual (col0 m1) (col0 m2) .&&
    almostVecEqual (col1 m1) (col1 m2) .&&
    almostVecEqual (col2 m1) (col2 m2) .&&
    almostVecEqual (col3 m1) (col3 m2)


-- Tests

genericTests :: [ExprTest d]
genericTests = [
        trivialTest,
        vec2Test,
        vec3Test,
        vec4Test,
        mat2Test,
        mat3Test,
        mat4Test,
        mat2x3Test,
        mat2x3Test,
        mat3x2Test,
        mat3x4Test,
        mat4x2Test,
        mat4x3Test,
        arrayTest,
        rawConstrTest,
        glLiftTest,
        castTest,
        matCastTest,
        numFloatTest,
        numIntTest,
        numUIntTest,
        numVecTest,
        numIntVecTest,
        numUIntVecTest,
        fractionalFloatTest,
        fractionalVecTest,
        floatingFloatTest,
        floatingVecTest,
        numOpsIntTest,
        numOpsUIntTest,
        numOpsVecTest,
        numOpsIntVecTest,
        modulusTest,
        scalarMultTest,
        matMultTest,
        booleanExprsTest,
        bitwiseExprsTest,
        lengthTest,
        distanceTest,
        dotTest,
        crossTest,
        normalizeTest,
        faceforwardTest,
        reflectTest,
        refractTest,
        vecArithmeticTest,
        glFuncTrivialTest,
        glFuncMultipleTest,
        glFuncNestedTest,
        glFuncFactTest,
        glFuncFibTest,
        glFuncCollatzTest,
        glFuncMandelbrotTest,
        glFuncNestedCondTest,
        uniformFloatTest,
        uniformIntTest,
        uniformUIntTest,
        uniformBoolTest,
        uniformVec2FloatTest,
        uniformVec3FloatTest,
        uniformVec4FloatTest,
        uniformVec2IntTest,
        uniformVec3IntTest,
        uniformVec4IntTest,
        uniformVec2UIntTest,
        uniformVec3UIntTest,
        uniformVec4UIntTest,
        uniformVec2BoolTest,
        uniformVec3BoolTest,
        uniformVec4BoolTest,
        uniformMat2Test,
        uniformMat3Test,
        uniformMat4Test,
        uniformMat2x3Test,
        uniformMat2x4Test,
        uniformMat3x2Test,
        uniformMat3x4Test,
        uniformMat4x2Test,
        uniformMat4x3Test,
        uniformFloatArrayTest,
        uniformVec2ArrayTest,
        uniformVec3ArrayTest,
        uniformVec4ArrayTest,
        uniformIntArrayTest,
        uniformIntVec2ArrayTest,
        uniformIntVec3ArrayTest,
        uniformIntVec4ArrayTest,
        uniformUIntArrayTest,
        uniformUIntVec2ArrayTest,
        uniformUIntVec3ArrayTest,
        uniformUIntVec4ArrayTest,
        uniformBoolArrayTest,
        uniformBoolVec2ArrayTest,
        uniformBoolVec3ArrayTest,
        uniformBoolVec4ArrayTest,
        exponentialExprTreeTest,
        iteratedGlFuncTest
    ]

hostTests :: [ExprTest HostDomain]
hostTests = [

    ]

shaderTests :: [ExprTest FragmentDomain]
shaderTests = [
        inputFloatTest,
        inputIntTest,
        inputUIntTest,
        inputVec2Test,
        inputVec3Test,
        inputVec4Test,
        inputIntVec2Test,
        inputIntVec3Test,
        inputIntVec4Test,
        inputUIntVec2Test,
        inputUIntVec3Test,
        inputUIntVec4Test,
        interpTest,
        precTrivialTest,
        precNestedTest,
        precIntegrateTest,
        --precTimeTest,
        precSequenceTest,
        precFiboTest
    ]

shaderExceptionTests :: [ExprExceptionTest]
shaderExceptionTests = [
        glLiftIllegalTest,
        glFuncFactIllegalTest,
        glFuncCollatzIllegalTest,
        glFuncNestedCondIllegalTest,
        glFuncNameCaptureIllegalTest,
        glFuncRecIllegalTest,
        glFuncMutRecIllegalTest
    ]

objTests :: [ObjTest]
objTests = [
        trivialImageTest,
        passAroundTest,
        multiObjOverlapTest,
        multiObjComplementTest,
        multiObjDiscardTest,
        multiObjSharedExprsTest
        --multiObjSharedHostExprsTest,
        --multiObjSharedPrecsTest
    ]

objExceptionTests :: [ObjExceptionTest]
objExceptionTests = [
        noInputVarsTest,
        emptyInputVarTest,
        mismatchedInputVarsTest
    ]


trivialTest = ExprTest "trivial" true


-- Vector and matrix (de-)construction and indexing

vec2Test = ExprTest "vec2" $
    let v = vec2 1 2 :: GLExpr d (Vec 2 Int)
        (decon -> (x, y)) = v
    in v .== 1 + vec2 0 1 .&&
       v .== 0 + 1 .# v + 0 .&&
       v .== vec2 1 0 + 2 .# vec2 0 1 .&&
       v .== vec2 (x_ v) (y_ v) .&&
       v .== vec2 x y .&&
       v .== (yx_ . yx_) v .&&
       v ./= (yx_ . xy_) v

vec3Test = ExprTest "vec3" $
    let v = vec3 1 2 3 :: GLExpr d (Vec 3 Int)
        (decon -> (x, y, z)) = v
    in v .== pre 1 (vec2 2 3) .&&
       v .== app (vec2 1 2) 3 .&&
       v .== 1 + vec3 0 1 2 .&&
       v .== 0 + 1.# v + 0 .&&
       v .== vec3 1 0 0 + 2 .# vec3 0 1 0 + 3 .# vec3 0 0 1 .&&
       v .== vec3 (x_ v) (y_ v) (z_ v) .&&
       v .== vec3 x y z .&&
       v .== pre x (vec2 y z) .&&
       v .== app (vec2 x y) z .&&
       v .== (zyx_ . zyx_) v .&&
       v ./= (zyx_ . xyz_) v

vec4Test = ExprTest "vec4" $
    let v = vec4 1 2 3 4 :: GLExpr d (Vec 4 Int)
        (decon -> (x, y, z, w)) = v
    in v .== vec2 1 2 $- vec2 3 4 .&&
       v .== pre 1 (vec3 2 3 4) .&&
       v .== app (vec3 1 2 3) 4 .&&
       v .== 1 + vec4 0 1 2 3 .&&
       v .== 0 + 1 .# v + 0 .&&
       v .== vec4 1 0 0 0 + 2 .# vec4 0 1 0 0 + 
             3 .# vec4 0 0 1 0 + 4 .# vec4 0 0 0 1 .&&
       v .== vec4 (x_ v) (y_ v) (z_ v) (w_ v) .&&
       v .== vec4 x y z w .&&
       v .== pre x (vec3 y z w) .&&
       v .== app (vec3 x y z) w .&&
       v .== pre x ((zyx_ . wzy_) v) .&&
       v .== app ((xyz_ . xyz_) v) w

mat2Test = ExprTest "mat2" $
    let m = mat2 (vec2 1 2) (vec2 3 4) :: GLExpr d (Mat 2 2 Float)
        (decon -> (c0, c1)) = m
    in almostMatPx2Equal m (mat2 (col0 m) (col1 m)) .&&
       almostMatPx2Equal m (mat2 c0 c1)

mat3Test = ExprTest "mat3" $
    let m = mat3 (vec3 1 2 3) (vec3 4 5 6) (vec3 7 8 9) :: GLExpr d (Mat 3 3 Float)
        (decon -> (c0, c1, c2)) = m
    in almostMatPx3Equal m (mat3 (col0 m) (col1 m) (col2 m)) .&&
       almostMatPx3Equal m (mat3 c0 c1 c2) {-.&&
       almostMatPx3Equal m ((mat3x2 c0 c1) $| c2) .&&
       almostMatPx3Equal m (c0 $| (mat3x2 c1 c2))-}

mat4Test = ExprTest "mat4" $
    let m = mat4 (vec4 1 2 3 4) (vec4 5 6 7 8) 
                 (vec4 9 10 11 12) (vec4 13 14 15 16) :: GLExpr d (Mat 4 4 Float)
        (decon -> (c0, c1, c2, c3)) = m
    in almostMatPx4Equal m (mat4 (col0 m) (col1 m) (col2 m) (col3 m)) .&&
       almostMatPx4Equal m (mat4 c0 c1 c2 c3) {-.&&
       almostMatPx4Equal m ((mat4x3 c0 c1 c2) $| c3) .&&
       almostMatPx4Equal m ((mat4x2 c0 c1) $| (mat4x2 c2 c3)) .&&
       almostMatPx4Equal m (c0 $| (mat4x3 c1 c2 c3))-}

mat2x3Test = ExprTest "mat2x3" $
    let m = mat2x3 (vec2 1 2) (vec2 3 4) (vec2 5 6) :: GLExpr d (Mat 2 3 Float)
        (decon -> (c0, c1, c2)) = m
    in almostMatPx3Equal m (mat2x3 (col0 m) (col1 m) (col2 m)) .&&
       almostMatPx3Equal m (mat2x3 c0 c1 c2)

mat2x4Test = ExprTest "mat2x4" $
    let m = mat2x4 (vec2 1 2) (vec2 3 4) (vec2 5 6) (vec2 7 8) :: GLExpr d (Mat 2 4 Float)
        (decon -> (c0, c1, c2, c3)) = m
    in almostMatPx4Equal m (mat2x4 (col0 m) (col1 m) (col2 m) (col3 m)) .&&
       almostMatPx4Equal m (mat2x4 c0 c1 c2 c3)

mat3x2Test = ExprTest "mat3x2" $
    let m = mat3x2 (vec3 1 2 3) (vec3 4 5 6) :: GLExpr d (Mat 3 2 Float)
        (decon -> (c0, c1)) = m
    in almostMatPx2Equal m (mat3x2 (col0 m) (col1 m)) .&&
       almostMatPx2Equal m (mat3x2 c0 c1)

mat3x4Test = ExprTest "mat3x4" $
    let m = mat3x4 (vec3 1 2 3) (vec3 4 5 6) (vec3 7 8 9) (vec3 10 11 12) :: GLExpr d (Mat 3 4 Float)
        (decon -> (c0, c1, c2, c3)) = m
    in almostMatPx4Equal m (mat3x4 (col0 m) (col1 m) (col2 m) (col3 m)) .&&
       almostMatPx4Equal m (mat3x4 c0 c1 c2 c3)

mat4x2Test = ExprTest "mat4x2" $
    let m = mat4x2 (vec4 1 2 3 4) (vec4 5 6 7 9) :: GLExpr d (Mat 4 2 Float)
        (decon -> (c0, c1)) = m
    in almostMatPx2Equal m (mat4x2 (col0 m) (col1 m)) .&&
       almostMatPx2Equal m (mat4x2 c0 c1)

mat4x3Test = ExprTest "mat4x3" $
    let m = mat4x3 (vec4 1 2 3 4) (vec4 5 6 7 8) (vec4 9 10 11 12) :: GLExpr d (Mat 4 3 Float)
        (decon -> (c0, c1, c2)) = m
    in almostMatPx3Equal m (mat4x3 (col0 m) (col1 m) (col2 m)) .&&
       almostMatPx3Equal m (mat4x3 c0 c1 c2)

-- Arrays

arrayTest = ExprTest "array" $
    let a1 = uniform $ array [cnst (2 * i) | i <- [0..999]] :: GLExpr d [Int]
        a2 = uniform $ array [vec2 1 1, vec2 2 2] :: GLExpr d [Vec 2 Int]
    in a1 .! 0 .== 0 .&& a1 .! 10 .== 2 * 10 .&&
       a1 .! 999 .== 2 * 999 .&&
       a1 .! (uniform 1) .== 2 .&&
       a2 .! 0 + a2 .! 0 .== a2 .! 1


-- Lifts from raw types

rawConstrTest = ExprTest "raw_constr" $
    let m0 = mat3 (vec3 1 4 7) (vec3 2 5 8) (vec3 3 6 9)
        m1 = uniform $ glLift0 $ fromList [1, 2, 3, 4, 5, 6, 7, 8, 9] :: GLExpr d (Mat 3 3 Float)
        m2 = uniform $ glLift0 $ fromMapping (\(i, j) -> fromIntegral $ 3 * i + j + 1) :: GLExpr d (Mat 3 3 Float)
        a0 = uniform $ array [1,2,3,4] :: GLExpr d [Int]
        a1 = uniform $ glLift0 [1,2,3,4] :: GLExpr d [Int]
        a2 = uniform $ glLift0 [4,3,2,1] :: GLExpr d [Int]
    in almostMatPx3Equal m0 m1 .&& almostMatPx3Equal m0 m2 .&& almostMatPx3Equal m1 m2 .&& 
       a0 .== a1 .&& a0 ./= a2

glLiftTest = ExprTest "glLift" $
    let f1 = glLift1 $ \x -> x + 1
        x1 = 1 :: GLExpr HostDomain Int
        y1 = 2 :: GLExpr HostDomain Int
        f2 = glLift1 $ \x -> [x, x]
        x2 = 1 :: GLExpr HostDomain (Vec 2 Int)
        a2 = uniform $ array [x2, x2]
        f3 = glLift2 $ \x y -> [x, y, x + y]
        x3 = 1 :: GLExpr HostDomain Int
        y3 = 2 :: GLExpr HostDomain Int
        a3 = uniform $ array [1,2,3] :: GLExpr d [Int]
    in uniform (f1 x1) .== 2 .&& uniform (f1 y1) .== 3 .&& 
       uniform (f2 x2) .== a2 .&& uniform (f3 x3 y3) .== a3

glLiftIllegalTest = ExprExceptionTest "glLift_illegal" UnknownArraySize $
    let f = glLift1 $ \n -> replicate n 0
        a = uniform $ array [0, 0, 0] :: GLExpr d [Int]
    in uniform (f 3) .== a


-- Type conversion

castTest = ExprTest "cast" $
    cast (2 :: GLExpr d Int) .== true .&&
    cast ((-1) :: GLExpr d Int) .== true .&&
    cast (0 :: GLExpr d Int) .== false .&&
    cast (2 :: GLExpr d UInt) .== true .&&
    cast (0 :: GLExpr d UInt) .== false .&&
    cast (1.7 :: GLExpr d Float) .== true .&&
    cast (0.7 :: GLExpr d Float) .== true .&&
    cast true .== (1 :: GLExpr d Int) .&&
    cast false .== (0 :: GLExpr d Int) .&&
    cast (1 :: GLExpr d UInt) .== (1 :: GLExpr d Int) .&&
    cast (1.7 :: GLExpr d Float) .== (1 :: GLExpr d Int) .&&
    cast ((-1.7) :: GLExpr d Float) .== ((-1) :: GLExpr d Int) .&&
    almostEqual (cast true) (1 :: GLExpr d Float) .&&
    almostEqual (cast false) (0 :: GLExpr d Float) .&&
    almostEqual (cast (1 :: GLExpr d Int)) (1 :: GLExpr d Float) .&&
    almostEqual (cast (1 :: GLExpr d UInt)) (1 :: GLExpr d Float)

matCastTest = ExprTest "matCast" $
    matCast (vec3 2 (-1) 0 :: GLExpr d (Vec 3 Int)) .== vec3 true true false .&&
    almostVecEqual (matCast $ vec2 true false) (vec2 1 0 :: GLExpr d (Vec 2 Float))


-- Num, Fractional, Floating

-- TODO: use QuickCheck to verify properties like these, wherever possible
checkNumProperties eq x y z =
    ((x + y) + z) `eq` (x + (y + z)) .&&
    (x + y) `eq` (y + x) .&&
    (x + fromInteger 0) `eq` x .&&
    (x + negate x) `eq` (fromInteger 0) .&&
    ((x * y) * z) `eq` (x * (y * z)) .&&
    (x * fromInteger 1) `eq` x .&&
    (fromInteger 1 * x) `eq` x .&&
    (x * (y + z)) `eq` ((x * y) + (x * z)) .&&
    ((y + z) * x) `eq` ((y * x) + (z * x)) .&&
    (abs x * signum x) `eq` x

numFloatTest = ExprTest "num_float" $
    checkNumProperties almostEqual (-1.234567 :: GLExpr d Float) 1.789012 (-1.567890)

numIntTest = ExprTest "num_int" $
    checkNumProperties (.==) (-1 :: GLExpr d Int) 7 (-5)

numUIntTest = ExprTest "num_uint" $
    checkNumProperties (.==) (1 :: GLExpr d UInt) 7 5

numVecTest = ExprTest "num_vec" $
    let v = vec4 1 2 3 4 :: GLExpr d (Vec 4 Float)
    in checkNumProperties almostVecEqual 
        (-1.234567 + v) (1.789012 + v) (-1.567890 + v)

numIntVecTest = ExprTest "num_ivec" $
    let v = vec4 1 2 3 4 :: GLExpr d (Vec 4 Int)
    in checkNumProperties (.==) v (7 + v) (-5 + v)

numUIntVecTest = ExprTest "num_uvec" $
    let v = vec4 1 2 3 4 :: GLExpr d (Vec 4 Int)
    in checkNumProperties (.==) v (7 + v) (5 + v)

checkFractionalProperties eq x y z = 
    (x * recip x) `eq` (recip x * x) .&&
    (x * recip x) `eq` (fromInteger 1) .&&
    (x * recip x) `eq` (fromRational (toRational 1)) .&&
    (x / y) `eq` (recip (y / x))

fractionalFloatTest = ExprTest "fractional_float" $
    checkFractionalProperties almostEqual (-1.234567 :: GLExpr d Float) 1.789012 (-1.567890)

fractionalVecTest = ExprTest "fractional_vec" $
    let v = vec4 1 2 3 4 :: GLExpr d (Vec 4 Float)
    in checkFractionalProperties almostVecEqual 
        (-1.234567 + v) (1.789012 + v) (-1.567890 + v)

checkFloatingProperties eq x y z = 
    (Prelude.exp (x + y)) `eq` (Prelude.exp x * Prelude.exp y) .&&
    (Prelude.exp (fromInteger 0)) `eq` (fromInteger 1) .&&
    (Prelude.log (abs $ x * y)) `eq` (Prelude.log (abs x) + Prelude.log (abs y)) .&&
    (Prelude.log (abs $ x / y)) `eq` (Prelude.log (abs x) - Prelude.log (abs y)) .&&
    (Prelude.sqrt (x ** 2)) `eq` (Prelude.abs x) .&&
    (x ** (-2)) `eq` recip (x ** 2) .&&
    ((Prelude.sin x ** 2) + (Prelude.cos x ** 2)) `eq` (fromInteger 1)

floatingFloatTest = ExprTest "floating_float" $
    checkFloatingProperties almostEqual (-1.234567 :: GLExpr d Float) 1.789012 (-1.567890)

floatingVecTest = ExprTest "floating_vec" $
    let v = vec4 0.1 0.2 0.3 0.4 :: GLExpr d (Vec 4 Float)
    in checkFloatingProperties almostVecEqual 
        (-1.234567 + v) (1.789012 + v) (-1.567890 + v)


-- Numeric operators

checkNumericOps eq zero one x y z =
    ((x + y) + z) `eq` (x + (y + z)) .&&
    (x + y) `eq` (y + x) .&&
    (x + zero) `eq` x .&&
    (x + neg x) `eq` zero .&&
    ((x * y) * z) `eq` (x * (y * z)) .&&
    (x * one) `eq` x .&&
    (one * x) `eq` x .&&
    (x * (y + z)) `eq` ((x * y) + (x * z)) .&&
    ((y + z) * x) `eq` ((y * x) + (z * x)) .&&
    (x ./ x) `eq` one .&&
    (x * y ./ x) `eq` y .&&
    (x * y ./ y) `eq` x

numOpsFloatTest = ExprTest "num_ops_float" $
    checkNumericOps almostEqual (0 :: GLExpr d Float) 1 (-1.234567) 1.789012 (-1.567890)

numOpsIntTest = ExprTest "num_ops_int" $
    checkNumericOps (.==) (0 :: GLExpr d Int) 1 (-1) 7 (-5) 

numOpsUIntTest = ExprTest "num_ops_uint" $
    checkNumericOps (.==) (0 :: GLExpr d UInt) 1 1 7 5

numOpsVecTest = ExprTest "num_ops_vec" $
    let v = vec4 1 2 3 4 :: GLExpr d (Vec 4 Float)
    in checkNumericOps almostVecEqual 0 1
        (-1.234567 + v) (1.789012 + v) (-1.567890 + v)

numOpsIntVecTest = ExprTest "num_ops_ivec" $
    let v = vec4 1 2 3 4 :: GLExpr d (Vec 4 Int)
    in checkNumericOps (.==) 0 1
        v (7 + v) (-5 + v)

modulusTest = ExprTest "modulus" $
    vec4 10 100 101 200 .% (100 :: GLExpr d (Vec 4 Int)) .== vec4 10 0 1 0 .&&
    (101 :: GLExpr d UInt) .% 100 .== 1

scalarMultTest = ExprTest "scalar_mult" $
    (2 :: GLExpr d Int) .# vec2 1 2 .== vec2 2 4 .&&
    (2 :: GLExpr d Int) .# vec3 1 2 3 .== vec3 2 4 6 .&&
    (2 :: GLExpr d Int) .# vec4 1 2 3 4.== vec4 2 4 6 8 .&&
    ((2 :: GLExpr d Float) .# vec2 1 2) `almostVecEqual` vec2 2 4 .&&
    ((2 :: GLExpr d Float) .# vec3 1 2 3) `almostVecEqual` vec3 2 4 6 .&&
    ((2 :: GLExpr d Float) .# vec4 1 2 3 4) `almostVecEqual` vec4 2 4 6 8 .&&
    ((2 :: GLExpr d Float) .# mat2x2 (vec2 0 1) (vec2 2 3)) `almostMatPx2Equal` 
        mat2x2 (vec2 0 2) (vec2 4 6) .&&
    ((2 :: GLExpr d Float) .# mat2x3 (vec2 0 1) (vec2 2 3) (vec2 4 5)) `almostMatPx3Equal` 
        mat2x3 (vec2 0 2) (vec2 4 6) (vec2 8 10) .&&
    ((2 :: GLExpr d Float) .# mat2x4 (vec2 0 1) (vec2 2 3) (vec2 4 5) (vec2 6 7)) `almostMatPx4Equal` 
        mat2x4 (vec2 0 2) (vec2 4 6) (vec2 8 10) (vec2 12 14) .&&
    ((2 :: GLExpr d Float) .# mat3x2 (vec3 0 1 2) (vec3 3 4 5)) `almostMatPx2Equal` 
        mat3x2 (vec3 0 2 4) (vec3 6 8 10) .&&
    ((2 :: GLExpr d Float) .# mat3x3 (vec3 0 1 2) (vec3 3 4 5) (vec3 6 7 8)) `almostMatPx3Equal` 
        mat3x3 (vec3 0 2 4) (vec3 6 8 10) (vec3 12 14 16) .&&
    ((2 :: GLExpr d Float) .# mat3x4 (vec3 0 1 2) (vec3 3 4 5) (vec3 6 7 8) (vec3 9 10 11)) `almostMatPx4Equal` 
        mat3x4 (vec3 0 2 4) (vec3 6 8 10) (vec3 12 14 16) (vec3 18 20 22) .&&
    ((2 :: GLExpr d Float) .# mat4x2 (vec4 0 1 2 3) (vec4 4 5 6 7)) `almostMatPx2Equal` 
        mat4x2 (vec4 0 2 4 6) (vec4 8 10 12 14) .&&
    ((2 :: GLExpr d Float) .# mat4x3 (vec4 0 1 2 3) (vec4 4 5 6 7) (vec4 8 9 10 11)) `almostMatPx3Equal` 
        mat4x3 (vec4 0 2 4 6) (vec4 8 10 12 14) (vec4 16 18 20 22) .&&
    ((2 :: GLExpr d Float) .# mat4x4 (vec4 0 1 2 3) (vec4 4 5 6 7) (vec4 8 9 10 11) (vec4 12 13 14 15)) `almostMatPx4Equal` 
        mat4x4 (vec4 0 2 4 6) (vec4 8 10 12 14) (vec4 16 18 20 22) (vec4 24 26 28 30)

matMultTest = ExprTest "mat_mult" $
    let m2x2 = mat2x2 (vec2 0 1) (vec2 2 3) :: GLExpr d (Mat 2 2 Float)
        m2x3 = mat2x3 (vec2 0 1) (vec2 2 3) (vec2 4 5) :: GLExpr d (Mat 2 3 Float)
        m2x4 = mat2x4 (vec2 0 1) (vec2 2 3) (vec2 4 5) (vec2 6 7) :: GLExpr d (Mat 2 4 Float)
        m3x2 = mat3x2 (vec3 0 1 2) (vec3 3 4 5) :: GLExpr d (Mat 3 2 Float)
        m3x3 = mat3x3 (vec3 0 1 2) (vec3 3 4 5) (vec3 6 7 8) :: GLExpr d (Mat 3 3 Float)
        m3x4 = mat3x4 (vec3 0 1 2) (vec3 3 4 5) (vec3 6 7 8) (vec3 9 10 11) :: GLExpr d (Mat 3 4 Float)
        m4x2 = mat4x2 (vec4 0 1 2 3) (vec4 4 5 6 7) :: GLExpr d (Mat 4 2 Float)
        m4x3 = mat4x3 (vec4 0 1 2 3) (vec4 4 5 6 7) (vec4 8 9 10 11) :: GLExpr d (Mat 4 3 Float)
        m4x4 = mat4x4 (vec4 0 1 2 3) (vec4 4 5 6 7) (vec4 8 9 10 11) (vec4 12 13 14 15) :: GLExpr d (Mat 4 4 Float)
    in (m2x2 .@ m2x2) `almostMatPx2Equal` mat2x2 (vec2 2 3) (vec2 6 11) .&&
       (m2x4 .@ m4x3) `almostMatPx3Equal` mat2x3 (vec2 28 34) (vec2 76 98) (vec2 124 162) .&&
       (m2x3 .@ m3x4) `almostMatPx4Equal` mat2x4 (vec2 10 13) (vec2 28 40) (vec2 46 67) (vec2 64 94) .&&
       (m3x3 .@ m3x2) `almostMatPx2Equal` mat3x2 (vec3 15 18 21) (vec3 42 54 66) .&&
       (m3x2 .@ m2x3) `almostMatPx3Equal` mat3x3 (vec3 3 4 5) (vec3 9 14 19) (vec3 15 24 33) .&&
       (m3x4 .@ m4x4) `almostMatPx4Equal` mat3x4 (vec3 42 48 54) (vec3 114 136 158) (vec3 186 224 262) (vec3 258 312 366) .&&
       (m4x4 .@ m4x2) `almostMatPx2Equal` mat4x2 (vec4 56 62 68 74) (vec4 152 174 196 218) .&&
       (m4x3 .@ m3x3) `almostMatPx3Equal` mat4x3 (vec4 20 23 26 29) (vec4 56 68 80 92) (vec4 92 113 134 155) .&&
       (m4x2 .@ m2x4) `almostMatPx4Equal` mat4x4 (vec4 4 5 6 7) (vec4 12 17 22 27) (vec4 20 29 38 47) (vec4 28 41 54 67)


-- Boolean and bitwise expressions

booleanExprsTest = ExprTest "boolean_expressions" $
    (-1 :: GLExpr d Int) .< 0 .&&
    (-1 :: GLExpr d Int) .<= 0 .&&
    (-1 :: GLExpr d Int) .<= -1 .&&
    (1 :: GLExpr d Int) .> 0 .&&
    (1 :: GLExpr d Int) .>= 0 .&&
    (1 :: GLExpr d Int) .>= 1 .&&
    (1 :: GLExpr d Int) .== 1 .&&
    (0 :: GLExpr d Int) ./= 1 .&&
    (true .&& true) .== true .&&
    (true .&& false) .== false .&&
    (false .&& true) .== false .&&
    (false .&& false) .== false .&&
    (true  .|| true) .== true .&&
    (true .|| false) .== true .&& 
    (false .|| true) .== true .&&
    (false .|| false) .== false .&&
    (true .^^ true) .== false .&&
    (true .^^ false) .== true .&&
    (false .^^ true) .== true .&&
    (false .^^ false) .== false .&&
    nt true .== false .&&
    nt false .== true .&&
    cond true (2 :: GLExpr d Int) 1 .== 2 .&&
    cond false (2 :: GLExpr d Int) 1 .== 1    

bitwiseExprsTest = ExprTest "bitwise_expressions" $
    (((7 :: GLExpr d Int) .<< 3) .>> 3) .== 7 .&&
    ((7 :: GLExpr d Int) .>> 3) .== 0 .&&
    ((12 :: GLExpr d Int) .& 10) .== 8 .&&
    ((12 :: GLExpr d Int) .| 10) .== 14 .&&
    ((12 :: GLExpr d Int) .^ 10) .== 6 .&&
    (compl . compl) (12345678 :: GLExpr d Int) .== 12345678


-- Common math functions



-- Geometric functions

lengthTest = ExprTest "length" $
    almostEqual (length (vec3 2 (-3) 6 :: GLExpr d (Vec 3 Float))) 7 .&&
    let v@(decon -> (x, y, z, w)) = vec4 1 2 3 4 :: GLExpr d (Vec 4 Float)
    in almostEqual (length v) (sqrt (x * x + y * y + z * z + w * w))

distanceTest = ExprTest "distance" $
    let x = vec4 1 2 3 4 :: GLExpr d (Vec 4 Float)
        y = vec4 5 6 7 8 :: GLExpr d (Vec 4 Float)
    in almostEqual (distance x y) (length (x - y))

dotTest = ExprTest "dot" $
    let theta = 0.1234 :: GLExpr d Float
        x = vec2 (cos theta) (sin theta) :: GLExpr d (Vec 2 Float)
        y = vec2 (cos $ theta + (pi / 2)) (sin $ theta + (pi / 2))
        z = vec4 1 2 3 4 :: GLExpr d (Vec 4 Float)
    in almostEqual (dot x y) 0 .&&
       almostEqual (dot z z) (length z * length z)

crossTest = ExprTest "cross" $
    true

normalizeTest = ExprTest "normalize" $
    let x = vec4 1 2 3 4 :: GLExpr d (Vec 4 Float)
    in almostVecEqual (normalize x) ((1 / length x) .# x)

faceforwardTest = ExprTest "faceforward" $
    true

reflectTest = ExprTest "reflect" $
    true

refractTest = ExprTest "refract" $
    true


-- Matrix functions



-- Vector relational functions



-- General numerical tests

vecArithmeticTest = ExprTest "vector_arithmetic" $
    2 .# vec4 1 2 3 4 - 3 * vec4 1 2 3 4 .== - (vec4 1 2 3 4 :: GLExpr d (Vec 4 Int)) .&&
    1 .# abs (vec4 1 1 1 1) - abs (-1) .== (vec4 0 0 0 0 :: GLExpr d (Vec 4 Int))


-- Custom function support via glFunc

glFuncTrivialTest = ExprTest "glFunc_trivial" $
    let f = glFunc2 $ \x y -> x + y
        x0 = 1 :: GLExpr d Int
        x1 = 2 :: GLExpr d Int
    in f x0 x1 .== 3

glFuncMultipleTest = ExprTest "glFunc_multiple" $
    let f1 :: GLExpr d Int -> GLExpr d Int
        f1 = glFunc1 (3 *)
        g1 :: GLExpr d Int -> GLExpr d Int
        g1 = glFunc1 (2 *)
        f2 :: GLExpr d Int -> GLExpr d Int -> GLExpr d Int
        f2 = glFunc2 $ \x y -> 2 * x * y
        g2 :: GLExpr d Int -> GLExpr d Int -> GLExpr d Int
        g2 = glFunc2 $ \x y -> 3 * x * y
    -- in the shader case, this should generate four non-main functions
    in f1 1 .> g1 1 .&& f1 1 .< g1 2 .&& f2 2 1 .> g2 1 1

glFuncNestedTest = ExprTest "glFunc_nested" $
    let f = glFunc2 $ \x y -> 2 * x + y
        g = glFunc3 $ \x y z -> z * f x y * (- f x y) * f y x
        x0 = 1 :: GLExpr d Int
        y0 = 2 :: GLExpr d Int
        z0 = 2 :: GLExpr d Int
    in g x0 y0 z0 .== -160

-- unsupported; flip arguments to cond as below
glFuncFactIllegalTest = ExprExceptionTest "glFunc_factorial_illegal" UnsupportedRecCall $ 
    let fact = glFunc1 $ \n -> fact' n 1
        fact' :: GLExpr d Int -> GLExpr d Int -> GLExpr d Int
        fact' = glFunc2 $ \n a -> cond (n ./= 0) (fact' (n - 1) (a * n)) a
    in fact 5 .== 120

glFuncFactTest = ExprTest "glFunc_factorial" $ 
    let fact = glFunc1 $ \n -> fact' n 1
        fact' :: GLExpr d Int -> GLExpr d Int -> GLExpr d Int
        fact' = glFunc2 $ \n a -> cond (n .== 0) a (fact' (n - 1) (a * n))
    in fact' 5 1 .== 120

glFuncFibTest = ExprTest "glFunc_fibonacci" $ 
    let fib = glFunc1 $ \n -> fib' n (vec2 0 1)
        fib' :: GLExpr d Int -> GLExpr d (Vec 2 Int) -> GLExpr d Int
        fib' = glFunc2 $ \n (decon -> (a, b)) -> cond (n .== 0) a (fib' (n - 1) (vec2 b (a + b)))
    in fib 6 .== 8

glFuncMandelbrotTest = ExprTest "glFunc_mandelbrot" $ 
    let mand :: GLExpr d (Vec 2 Float) -> GLExpr d (Vec 2 Float) -> GLExpr d Int -> GLExpr d Int
        mand = glFunc3 $ \pos0@(decon -> (x0, y0)) (decon -> (x, y)) i -> 
            cond (i .>= 50 .|| ((x * x + y * y) .> 4)) i $
                mand pos0 (vec2 (x * x - y * y + x0) (2 * x * y + y0)) (i + 1)
    in mand (vec2 3 0) (vec2 0 0) 0 .== 1 .&& 
       mand (vec2 0 0) (vec2 0 0) 0 .== 50

-- unsupported; canonicalize as in test below
glFuncCollatzIllegalTest = ExprExceptionTest "glFunc_collatz_illegal" UnsupportedRecCall $
    let f :: GLExpr d Int -> GLExpr d Int -> GLExpr d Int
        f = glFunc2 $ \n i -> cond (n .== 1) i $
            cond (n .% 2 .== 0) (f (n ./ 2) (i + 1)) (f (3 * n + 1) (i + 1))
    in f 27 0 .== 111

glFuncCollatzTest = ExprTest "glFunc_collatz" $
    let f :: GLExpr d Int -> GLExpr d Int -> GLExpr d Int
        f = glFunc2 $ \n i -> cond (n .== 1) i $
            f (cond (n .% 2 .== 0) (n ./ 2) (3 * n + 1)) (i + 1)
    in f 7 1 .== 17

-- unsupported; canonicalize as in test below
glFuncNestedCondIllegalTest = ExprExceptionTest "glFunc_nested_cond_illegal" UnsupportedRecCall $
    let f :: GLExpr d Int -> GLExpr d Int
        f = glFunc1 $ \n -> (1 +) $
            cond (n .== 1) 1 $
                cond (n .== 2) 2 $
                    cond (n .== 3) (f (n - 1)) $
                        cond  (n .== 4) (f (n - 3)) (f (n - 1))
    in f 1 .== 2 .&& f 2 .== 3 .&& f 3 .== 4 .&& f 4 .== 3 .&& f 10 .== 9

glFuncNestedCondTest = ExprTest "glFunc_nested_cond" $
    let f = glFunc1 $ \n -> f' n 0
        f' :: GLExpr d Int -> GLExpr d Int -> GLExpr d Int
        f' = glFunc2 $ \n a -> 
            cond (n .<= 4) (
                cond (n .== 1) (2 + a) $
                cond (n .== 2) (3 + a) $
                cond (n .== 3) (4 + a) (3 + a)
                ) $ f' (n - 1) (a + 1)
    in f 1 .== 2 .&& f 2 .== 3 .&& f 3 .== 4 .&& f 4 .== 3 .&& f 10 .== 9

-- currently unsupported
glFuncNameCaptureIllegalTest = ExprExceptionTest "glFunc_name_capture_illegal" UnsupportedNameCapture $
    let f :: GLExpr d Int -> GLExpr d Int -> GLExpr d Int
        f = glFunc2 $ \x u -> 
            let g = glFunc2 $ \y v -> x + y + u + v
            in g 2 0
    in f 1 0 .== 3

glFuncRecIllegalTest = ExprExceptionTest "glFunc_rec_call_illegal" UnsupportedRecCall $
    let f :: GLExpr d Int -> GLExpr d Int -> GLExpr d Int
        f = glFunc2 $ \x y -> f x (y + 1) + 1
    in f 0 0 .== 0

glFuncMutRecIllegalTest = ExprExceptionTest "glFunc_mut_rec_call_illegal" UnsupportedRecCall $
    let f :: GLExpr d Int -> GLExpr d Int
        f = glFunc1 $ \x -> g x + 1
        g = glFunc1 $ \x -> f x + 1
    in f 0 .== 0


-- uniform, prec, & built-in I/O variables

-- TODO: test double-precision once supported

uniformFloatTest = ExprTest "uniform_float" $
    almostEqual (uniform (1.2345 :: GLExpr d Float)) 1.2345

uniformIntTest = ExprTest "uniform_int" $
    let x = 2_000_000_000 :: GLExpr d Int
    in uniform x .== x

uniformUIntTest = ExprTest "uniform_uint" $
    let x = 4_000_000_000 :: GLExpr d UInt
    in uniform x .== x

uniformBoolTest = ExprTest "uniform_bool" $
    uniform true .== true .&& uniform false .== false

uniformVec2FloatTest = ExprTest "uniform_vec2" $
    let x = 1.234567 + vec2 1 2 :: GLExpr d (Vec 2 Float)
    in almostVecEqual (uniform x) x

uniformVec3FloatTest = ExprTest "uniform_vec3" $
    let x = 1.234567 + vec3 1 2 3 :: GLExpr d (Vec 3 Float)
    in almostVecEqual (uniform x) x

uniformVec4FloatTest = ExprTest "uniform_vec4" $
    let x = 1.234567 + vec4 1 2 3 4 :: GLExpr d (Vec 4 Float)
    in almostVecEqual (uniform x) x

uniformVec2IntTest = ExprTest "uniform_ivec2" $
    let x = 2_000_000_000 + vec2 1 2 :: GLExpr d (Vec 2 Int)
    in uniform x .== x

uniformVec3IntTest = ExprTest "uniform_ivec3" $
    let x = 2_000_000_000 + vec3 1 2 3 :: GLExpr d (Vec 3 Int)
    in uniform x .== x

uniformVec4IntTest = ExprTest "uniform_ivec4" $
    let x = 2_000_000_000 + vec4 1 2 3 4 :: GLExpr d (Vec 4 Int)
    in uniform x .== x

uniformVec2UIntTest = ExprTest "uniform_uvec2" $
    let x = vec2 4_000_000_000 4_000_000_001 :: GLExpr d (Vec 2 UInt)
    in uniform x .== x

uniformVec3UIntTest = ExprTest "uniform_uvec3" $
    let x = vec3 4_000_000_001 4_000_000_002 4_000_000_003 :: GLExpr d (Vec 3 UInt)
    in uniform x .== x

uniformVec4UIntTest = ExprTest "uniform_uvec4" $
    let x = vec4 4_000_000_001 4_000_000_002 4_000_000_003 4_000_000_004 :: GLExpr d (Vec 4 UInt)
    in uniform x .== x

uniformVec2BoolTest = ExprTest "uniform_bvec2" $
    let x = vec2 true false :: GLExpr d (Vec 2 Bool)
    in uniform x .== x

uniformVec3BoolTest = ExprTest "uniform_bvec3" $
    let x = vec3 false true false :: GLExpr d (Vec 3 Bool)
    in uniform x .== x

uniformVec4BoolTest = ExprTest "uniform_bvec4" $
    let x = vec4 true false true false :: GLExpr d (Vec 4 Bool)
    in uniform x .== x

uniformMat2Test = ExprTest "uniform_mat2" $
    let m = mat2 (vec2 1 2) (vec2 3 4) :: GLExpr d (Mat 2 2 Float)
    in almostMatPx2Equal (uniform m) m

uniformMat3Test = ExprTest "uniform_mat3" $
    let m = mat3 (vec3 1 2 3) (vec3 4 5 6) (vec3 7 8 9) :: GLExpr d (Mat 3 3 Float)
    in almostMatPx3Equal (uniform m) m

uniformMat4Test = ExprTest "uniform_mat4" $
    let m = mat4 (vec4 1 2 3 4) (vec4 5 6 7 8) (vec4 9 10 11 12) (vec4 13 14 15 16) :: GLExpr d (Mat 4 4 Float)
    in almostMatPx4Equal (uniform m) m

uniformMat2x3Test = ExprTest "uniform_mat2x3" $
    let m = mat2x3 (vec2 1 2) (vec2 3 4) (vec2 5 6) :: GLExpr d (Mat 2 3 Float)
    in almostMatPx3Equal (uniform m) m

uniformMat2x4Test = ExprTest "uniform_mat2x4" $
    let m = mat2x4 (vec2 1 2) (vec2 3 4) (vec2 5 6) (vec2 7 8) :: GLExpr d (Mat 2 4 Float)
    in almostMatPx4Equal (uniform m) m

uniformMat3x2Test = ExprTest "uniform_mat3x2" $
    let m = mat3x2 (vec3 1 2 3) (vec3 4 5 6) :: GLExpr d (Mat 3 2 Float)
    in almostMatPx2Equal (uniform m) m

uniformMat3x4Test = ExprTest "uniform_mat3x4" $
    let m = mat3x4 (vec3 1 2 3) (vec3 4 5 6) (vec3 7 8 9) (vec3 10 11 12) :: GLExpr d (Mat 3 4 Float)
    in almostMatPx4Equal (uniform m) m

uniformMat4x2Test = ExprTest "uniform_mat4x2" $
    let m = mat4x2 (vec4 1 2 3 4) (vec4 5 6 7 8) :: GLExpr d (Mat 4 2 Float)
    in almostMatPx2Equal (uniform m) m

uniformMat4x3Test = ExprTest "uniform_mat4x3" $
    let m = mat4x3 (vec4 1 2 3 4) (vec4 5 6 7 8) (vec4 9 10 11 12) :: GLExpr d (Mat 4 3 Float)
    in almostMatPx3Equal (uniform m) (mat4x3 (vec4 1 2 3 4) (vec4 5 6 7 8) (vec4 9 10 11 12))

uniformFloatArrayTest = ExprTest "uniform_float[]" $
    let a = map (+ 0.2345) [1, 2, 3, 4] :: [GLExpr d Float]
    in foldr (\i e -> e .&& almostEqual (uniform (array a) .! cnst i) (a !! (fromEnum i))) true [0..3]
    
uniformVec2ArrayTest = ExprTest "uniform_vec2[]" $
    let a = map (+ 0.2345) [vec2 1 2, vec2 3 4, vec2 5 6, vec2 7 8] :: [GLExpr d (Vec 2 Float)]
    in foldr (\i e -> e .&& almostVecEqual (uniform (array a) .! cnst i) (a !! (fromEnum i))) true [0..3]
    
uniformVec3ArrayTest = ExprTest "uniform_vec3[]" $
    let a = map (+ 0.2345) [vec3 1 2 3, vec3 4 5 6, vec3 7 8 9, vec3 10 11 12] :: [GLExpr d (Vec 3 Float)]
    in foldr (\i e -> e .&& almostVecEqual (uniform (array a) .! cnst i) (a !! (fromEnum i))) true [0..3]
    
uniformVec4ArrayTest = ExprTest "uniform_vec4[]" $
    let a = map (+ 0.2345) [vec4 1 2 3 4, vec4 5 6 7 8, vec4 9 10 11 12, vec4 13 14 15 16] :: [GLExpr d (Vec 4 Float)]
    in foldr (\i e -> e .&& almostVecEqual (uniform (array a) .! cnst i) (a !! (fromEnum i))) true [0..3]

uniformIntArrayTest = ExprTest "uniform_int[]" $
    let a = map (+ 2_000_000_000) [1, 2, 3, 4] :: [GLExpr d Int]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]
    
uniformIntVec2ArrayTest = ExprTest "uniform_ivec2[]" $
    let a = map (+ 2_000_000_000) [vec2 1 2, vec2 3 4, vec2 5 6, vec2 7 8] :: [GLExpr d (Vec 2 Int)]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]
    
uniformIntVec3ArrayTest = ExprTest "uniform_ivec3[]" $
    let a = map (+ 2_000_000_000) [vec3 1 2 3, vec3 4 5 6, vec3 7 8 9, vec3 10 11 12] :: [GLExpr d (Vec 3 Int)]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]
    
uniformIntVec4ArrayTest = ExprTest "uniform_ivec4[]" $
    let a = map (+ 2_000_000_000) [vec4 1 2 3 4, vec4 5 6 7 8, vec4 9 10 11 12, vec4 13 14 15 16] :: [GLExpr d (Vec 4 Int)]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]

uniformUIntArrayTest = ExprTest "uniform_uint[]" $
    let a = map (+ 4_000_000_000) [1, 2, 3, 4] :: [GLExpr d UInt]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]
    
uniformUIntVec2ArrayTest = ExprTest "uniform_uvec2[]" $
    let a = map (+ 4_000_000_000) [vec2 1 2, vec2 5 6, vec2 9 10, vec2 13 14] :: [GLExpr d (Vec 2 UInt)]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]
    
uniformUIntVec3ArrayTest = ExprTest "uniform_uvec3[]" $
    let a = map (+ 4_000_000_000) [vec3 1 2 3, vec3 5 6 7, vec3 9 10 11, vec3 13 14 15] :: [GLExpr d (Vec 3 UInt)]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]
    
uniformUIntVec4ArrayTest = ExprTest "uniform_uvec4[]" $
    let a = map (+ 4_000_000_000) [vec4 1 2 3 4, vec4 5 6 7 8, vec4 9 10 11 12, vec4 13 14 15 16] :: [GLExpr d (Vec 4 UInt)]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]

uniformBoolArrayTest = ExprTest "uniform_bool[]" $
    let a = [true, false, true, false] :: [GLExpr d Bool]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]
    
uniformBoolVec2ArrayTest = ExprTest "uniform_bvec2[]" $
    let a = [vec2 true true, vec2 true false, vec2 false true, vec2 false false] :: [GLExpr d (Vec 2 Bool)]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]
    
uniformBoolVec3ArrayTest = ExprTest "uniform_bvec3[]" $
    let a = [vec3 true true true, vec3 true true false, vec3 true false true, vec3 true false false] :: [GLExpr d (Vec 3 Bool)]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]
    
uniformBoolVec4ArrayTest = ExprTest "uniform_bvec4[]" $
    let a = [vec4 true true true true, vec4 true true true false, vec4 true true false true, vec4 true true false false] :: [GLExpr d (Vec 4 Bool)]
    in foldr (\i e -> e .&& uniform (array a) .! cnst i .== a !! (fromEnum i)) true [0..3]

precTrivialTest = ExprTest "prec_trivial" $
    let x = prec (0 :: GLExpr d Int) x
        x1 = prec (0 :: GLExpr d Int) (x1 + 1)
        y1 = prec (0 :: GLExpr d Int) (y1 + 1)
    in uniform x .== 0 .&& uniform x1 .> 0 .&& uniform x1 .== uniform y1

precNestedTest = ExprTest "prec_nested" $
    let x = prec (0 :: GLExpr d Int) (x + 1)
        y = prec (-1 :: GLExpr d Int) x
    in uniform y .> 0

precIntegrateTest = ExprTest "prec_integrate" $
    let t = prec (0 :: GLExpr d Int) (t + 1)
        x = prec (0 :: GLExpr d Int) (x + t)
    in uniform t .> 0 .&&
       2 * uniform x .== uniform t * (uniform t - 1)

precTimeTest = ExprTest "prec_time" $
    let dt = prec 0 (time - prec 0 time)
        dt_sum = prec 0 (dt_sum + dt)
    in almostEqual (uniform $ prec 0 time) (uniform dt_sum)

precSequenceTest = ExprTest "prec_sequence" $
    let t = prec (0 :: GLExpr d Int) (t + 1)
        tp = uniform $ array $ take 20 $ iterate (\t -> prec 0 t) t
    in foldr (\i e -> e .&& tp .! i .== uniform t - i) true (map cnst [0..19])

precFiboTest = ExprTest "prec_fibonacci" $
    let fibSeq = prec (0 :: GLExpr d Int) (fibSeq + prec 1 fibSeq)
        fibSeq' = prec 0 fibSeq' + prec 0 (prec 1 fibSeq')
    in uniform fibSeq .== uniform fibSeq'

-- Shader-specific tests

-- TODO: test double-precision once supported

inputFloatTest = ExprTest "input_float" $
    let x = map (+ 0.2345) [1, 2, 3, 4] :: [GLExpr d Float]
    in almostEqual (frag (vert x)) (frag (1 + vert (map (\x -> x - 1) x)))

inputIntTest = ExprTest "input_int" $
    let x = map (+ 2_000_000_000) [1, 2, 3, 4] :: [GLExpr d Int]
    in flatFrag (vert x) .== flatFrag (1 + vert (map (\x -> x - 1) x))

inputUIntTest = ExprTest "input_uint" $
    let x = map (+ 4_000_000_000) [1, 2, 3, 4] :: [GLExpr d UInt]
    in flatFrag (vert x) .== flatFrag (1 + vert (map (\x -> x - 1) x))

inputVec2Test = ExprTest "input_vec2" $
    let x = map (+ 0.2345) [vec2 1 2, vec2 3 4, vec2 5 6, vec2 7 8] :: [GLExpr d (Vec 2 Float)]
    in almostVecEqual (frag (vert x)) (frag (1 + vert (map (\x -> x - 1) x)))

inputVec3Test = ExprTest "input_vec3" $
    let x = map (+ 0.2345) [vec3 1 2 3, vec3 4 5 6, vec3 7 8 9, vec3 10 11 12] :: [GLExpr d (Vec 3 Float)]
    in almostVecEqual (frag (vert x)) (frag (1 + vert (map (\x -> x - 1) x)))

inputVec4Test = ExprTest "input_vec4" $
    let x = map (+ 0.2345) [vec4 1 2 3 4, vec4 5 6 7 8, vec4 9 10 11 12, vec4 13 14 15 16] :: [GLExpr d (Vec 4 Float)]
    in almostVecEqual (frag (vert x)) (frag (1 + vert (map (\x -> x - 1) x)))

inputIntVec2Test = ExprTest "input_ivec2" $
    let x = map (+ 2_000_000_000) [vec2 1 2, vec2 3 4, vec2 5 6, vec2 7 8] :: [GLExpr d (Vec 2 Float)]
    in flatFrag (vert x) .== flatFrag (1 + vert (map (\x -> x - 1) x))

inputIntVec3Test = ExprTest "input_ivec3" $
    let x = map (+ 2_000_000_000) [vec3 1 2 3, vec3 4 5 6, vec3 7 8 9, vec3 10 11 12] :: [GLExpr d (Vec 3 Float)]
    in flatFrag (vert x) .== flatFrag (1 + vert (map (\x -> x - 1) x))

inputIntVec4Test = ExprTest "input_ivec4" $
    let x = map (+ 2_000_000_000) [vec4 1 2 3 4, vec4 5 6 7 8, vec4 9 10 11 12, vec4 13 14 15 16] :: [GLExpr d (Vec 4 Float)]
    in flatFrag (vert x) .== flatFrag (1 + vert (map (\x -> x - 1) x))

inputUIntVec2Test = ExprTest "input_uvec2" $
    let x = map (+ 4_000_000_000) [vec2 1 2, vec2 3 4, vec2 5 6, vec2 7 8] :: [GLExpr d (Vec 2 Float)]
    in flatFrag (vert x) .== flatFrag (1 + vert (map (\x -> x - 1) x))

inputUIntVec3Test = ExprTest "input_uvec3" $
    let x = map (+ 4_000_000_000) [vec3 1 2 3, vec3 4 5 6, vec3 7 8 9, vec3 10 11 12] :: [GLExpr d (Vec 3 Float)]
    in flatFrag (vert x) .== flatFrag (1 + vert (map (\x -> x - 1) x))

inputUIntVec4Test = ExprTest "input_uvec4" $
    let x = map (+ 4_000_000_000) [vec4 1 2 3 4, vec4 5 6 7 8, vec4 9 10 11 12, vec4 13 14 15 16] :: [GLExpr d (Vec 4 Float)]
    in flatFrag (vert x) .== flatFrag (1 + vert (map (\x -> x - 1) x))

interpTest = ExprTest "basic_interpolation_properties" $
    let x = vert [1, 2, 3, 4] :: VertExpr Float
        x' = vert $ map (\x -> 2 * x + 1) [1, 2, 3, 4]
        y = frag x
        z = 2 * noperspFrag x + 1
        z' = noperspFrag x'
        w = flatFrag x
    in y .>=1 .&& y .<= 4 .&& 
       almostEqual z z' .&& 
       (w .== 1 .|| w .== 2 .|| w .== 3 .|| w .== 4)


-- Object-level tests

trivialImageTest = ObjTest "trivial_image" $ return $ fromImage $ \pos ->
    vec4 1 1 1 1

quad = 
    [vec2 (-1) (-1), 
     vec2 (-1) 1, 
     vec2 1 (-1), 
     vec2 1 1]

passAroundTest = ObjTest "pass_around" [obj] where
    ppos = vert quad
    npos = vert $ map negate quad

    pos = ppos $- vec2 0 1
    fppos = frag ppos
    fppos' = frag (-npos)
    fppos'' = -(frag npos)
    fnpos = frag npos
    fnpos' = frag (-ppos)
    fnpos'' = -(frag ppos)
    color = (cast $ 
        fppos   + fnpos   .== 0 .&& 
        fppos   + fnpos'  .== 0 .&&
        fppos   + fnpos'' .== 0 .&&
        fppos'  + fnpos   .== 0 .&& 
        fppos'  + fnpos'  .== 0 .&&
        fppos'  + fnpos'' .== 0 .&&
        fppos'' + fnpos   .== 0 .&& 
        fppos'' + fnpos'  .== 0 .&&
        fppos'' + fnpos'' .== 0) .# 1
    obj = triangleStrip { position = pos, color = color }

multiObjOverlapTest = ObjTest "multi_obj_overlap" [obj1, obj2] where
    pos = vert quad
    obj1 = triangleStrip { position = pos $- vec2 0 1, color = 0 }
    obj2 = triangleStrip { position = pos $- vec2 0 1, color = 1 }

multiObjComplementTest = ObjTest "multi_obj_complement" [obj1, obj2] where
    pos1 = vert 
        [vec2 (-1) 0, 
         vec2 (-1) 1, 
         vec2 1 0, 
         vec2 1 1]
    pos2 = vert 
        [vec2 (-1) (-1), 
         vec2 (-1) 0, 
         vec2 1 (-1), 
         vec2 1 0]
    c1 = cast (y_ (frag pos1) .>= 0) .# 1
    c2 = cast (y_ (frag pos2) .< 0) .# 1
    obj1 = triangleStrip { position = pos1 $- vec2 0 1, color = c1 }
    obj2 = triangleStrip { position = pos2 $- vec2 0 1, color = c2 }

multiObjDiscardTest = ObjTest "multi_obj_discard" [obj1, obj2] where
    pos = vert quad
    c1 = cast (y_ (frag pos) .>= 0) .# 1
    d1 = y_ (frag pos) .< 0
    c2 = cast (y_ (frag pos) .< 0) .# 1
    d2 = y_ (frag pos) .>= 0
    obj1 = triangleStrip { position = pos $- vec2 0 1, color = c1, discardWhen = d1 }
    obj2 = triangleStrip { position = pos $- vec2 0 1, color = c2, discardWhen = d2 }

multiObjSharedExprsTest = ObjTest "multi_obj_shared_exprs" [obj1, obj2] where
    pos = vert quad
    c = 0.5 .# 1 + 0.5 .# 1
    d1 = y_ (frag pos) .< 0
    d2 = y_ (frag pos) .>= 0
    obj1 = triangleStrip { position = pos $- vec2 0 1, color = c, discardWhen = d1 }
    obj2 = triangleStrip { position = pos $- vec2 0 1, color = c, discardWhen = d2 }

-- TODO: the next two tests should be re-eningeered properly
-- the first is expected to fail and the second should pass
multiObjSharedHostExprsTest = ObjTest "multi_obj_shared_host_exprs" [obj1, obj2] where
    pos = vert quad
    a = array [(cast . floor $ time * 100000000) 
            .% cnst (i + 1) | i <- [0..99]] :: HostExpr [Int]
    x = (uniform a .! ((cast $ 1000 * x_ (frag pos)) .% 100)) .% 2 .== 0
    -- if obj1 and obj2 compute x separately then it is unlikely that
    -- c1 and c2 will agree
    c1 = cast x .# 1
    c2 = cast (nt x) .# 1
    d2 = x
    obj1 = triangleStrip { position = pos $- vec2 0 1, color = c1 }
    obj2 = triangleStrip { position = pos $- vec2 0 1, color = c2, discardWhen = d2 }

multiObjSharedPrecsTest = ObjTest "multi_obj_shared_precs" [obj1, obj2] where
    pos = vert quad
    a = array [(cast . floor $ time * 100000000) 
            .% cnst (i + 1) | i <- [0..99]] :: HostExpr [Int]
    x = (uniform a .! ((cast $ 1000 * x_ (frag pos)) .% 100)) .% 2 .== 0
    c1 = cast x .# 1
    c2 = cast (nt x) .# 1
    d2 = x
    obj1 = triangleStrip { position = pos $- vec2 0 1, color = c1 }
    obj2 = triangleStrip { position = pos $- vec2 0 1, color = c2, discardWhen = d2 }

noInputVarsTest = ObjExceptionTest "no_input_vars" NoInputVars $
    [triangleStrip]

emptyInputVarTest = ObjExceptionTest "empty_input_var" EmptyInputVar $
    [triangleStrip { position = vert [] }]

mismatchedInputVarsTest = ObjExceptionTest "mismatched_input_vars" MismatchedInputVars $
    [triangleStrip { position = vert [1, 1], color = frag (vert [1, 1, 1]) }]


-- Caching tests

exponentialExprTreeTest = ExprTest "exponential_expr_tree" $
    let f x = x + x 
        h = 40
    -- this is infeasible without caching as the entire
    -- expression tree of size 2^h will have to be traversed
    in iterate f (1 :: GLExpr d Int) !! h .== 2^h

iteratedGlFuncTest = ExprTest "iterated_glFunc" $
    let f = glFunc1 $ \x -> x + x 
        h = 40
    in iterate f (1 :: GLExpr d Int) !! h .== 2^h


-- Examples (verify output manually)

mkTestExample (label, objs) = TestLabel label $ TestCase $
    runObjs True label objs >>= (const $ assert True)


runTests :: Test -> IO ()
runTests tests = do
    count <- runTestTT tests
    if errors count > 0 || failures count > 0 then exitFailure else return ()

main :: IO ()
main = do
    createDirectoryIfMissing True "output/test"
    runTests $ TestList $ map mkHostTest genericTests
    runTests $ TestList $ map mkHostTest hostTests
    runTests $ TestList $ map mkShaderTest genericTests
    runTests $ TestList $ map mkShaderTest shaderTests
    runTests $ TestList $ map mkShaderExceptionTest shaderExceptionTests
    runTests $ TestList $ map mkObjTest objTests
    runTests $ TestList $ map mkObjExceptionTest objExceptionTests