hydra-0.1.0: src/test/haskell/Hydra/Adapters/TermSpec.hs
module Hydra.Adapters.TermSpec where
import Hydra.All
import Hydra.Adapters.Term
import Hydra.Adapters.UtilsEtc
import Hydra.Impl.Haskell.Dsl.Terms as Terms
import Hydra.Meta
import qualified Hydra.Impl.Haskell.Dsl.Types as Types
import Hydra.TestData
import Hydra.TestUtils
import qualified Test.Hspec as H
import qualified Data.Map as M
import qualified Test.QuickCheck as QC
import qualified Data.Set as S
import qualified Data.Maybe as Y
constraintsAreAsExpected :: H.SpecWith ()
constraintsAreAsExpected = H.describe "Verify that the language constraints include/exclude the appropriate types" $ do
H.it "int16 and int32 are supported in the test context" $ do
typeIsSupported (context [TypeVariantLiteral]) Types.int16 `H.shouldBe` True
typeIsSupported (context [TypeVariantLiteral]) Types.int32 `H.shouldBe` True
H.it "int8 and bigint are unsupported in the test context" $ do
typeIsSupported (context [TypeVariantLiteral]) Types.int8 `H.shouldBe` False
typeIsSupported (context [TypeVariantLiteral]) Types.bigint `H.shouldBe` False
H.it "Records are supported, but unions are not" $ do
typeIsSupported (context [TypeVariantLiteral, TypeVariantRecord]) latLonType `H.shouldBe` True
typeIsSupported (context [TypeVariantLiteral, TypeVariantRecord]) stringOrIntType `H.shouldBe` False
H.it "Records are supported if and only if each of their fields are supported" $ do
typeIsSupported (context [TypeVariantLiteral, TypeVariantRecord])
(TypeRecord $ RowType (Name "Example") Nothing [Types.field "first" Types.string, Types.field "second" Types.int16])
`H.shouldBe` True
typeIsSupported (context [TypeVariantLiteral, TypeVariantRecord])
(TypeRecord $ RowType (Name "Example") Nothing [Types.field "first" Types.string, Types.field "second" Types.int8])
`H.shouldBe` False
H.it "Lists are supported if the list element type is supported" $ do
typeIsSupported (context [TypeVariantLiteral, TypeVariantList]) listOfStringsType `H.shouldBe` True
typeIsSupported (context [TypeVariantLiteral, TypeVariantList]) listOfListsOfStringsType `H.shouldBe` True
typeIsSupported (context [TypeVariantLiteral, TypeVariantList]) listOfSetOfStringsType `H.shouldBe` False
where
context = languageConstraints . adapterContextTarget . termTestContext
supportedConstructorsAreUnchanged :: H.SpecWith ()
supportedConstructorsAreUnchanged = H.describe "Verify that supported term constructors are unchanged" $ do
H.it "Strings (and other supported literal values) pass through without change" $
QC.property $ \b -> checkDataAdapter
[TypeVariantLiteral]
Types.string
Types.string
False
(string b)
(string b)
H.it "Lists (when supported) pass through without change" $
QC.property $ \strings -> checkDataAdapter
[TypeVariantLiteral, TypeVariantList]
listOfStringsType
listOfStringsType
False
(list $ string <$> strings)
(list $ string <$> strings)
H.it "Maps (when supported) pass through without change" $
QC.property $ \keyvals -> checkDataAdapter
[TypeVariantLiteral, TypeVariantMap]
mapOfStringsToIntsType
mapOfStringsToIntsType
False
(makeMap keyvals)
(makeMap keyvals)
H.it "Optionals (when supported) pass through without change" $
QC.property $ \mi -> checkDataAdapter
[TypeVariantLiteral, TypeVariantOptional]
optionalInt8Type
optionalInt16Type
False
(optional $ int8 <$> mi)
(optional $ int16 . fromIntegral <$> mi)
H.it "Records (when supported) pass through without change" $
QC.property $ \a1 a2 -> checkDataAdapter
[TypeVariantLiteral, TypeVariantRecord]
(TypeRecord $ RowType testTypeName Nothing [Types.field "first" Types.string, Types.field "second" Types.int8])
(TypeRecord $ RowType testTypeName Nothing [Types.field "first" Types.string, Types.field "second" Types.int16])
False
(record testTypeName [field "first" $ string a1, field "second" $ int8 a2])
(record testTypeName [field "first" $ string a1, field "second" $ int16 $ fromIntegral a2])
H.it "Unions (when supported) pass through without change" $
QC.property $ \int -> checkDataAdapter
[TypeVariantLiteral, TypeVariantUnion]
stringOrIntType
stringOrIntType
False
(variant stringOrIntName (FieldName "right") $ int32 int)
(variant stringOrIntName (FieldName "right") $ int32 int)
H.it "Sets (when supported) pass through without change" $
QC.property $ \strings -> checkDataAdapter
[TypeVariantLiteral, TypeVariantSet]
setOfStringsType
setOfStringsType
False
(stringSet strings)
(stringSet strings)
H.it "Element references (when supported) pass through without change" $
QC.property $ \name -> checkDataAdapter
[TypeVariantElement]
int32ElementType
int32ElementType
False
(element name)
(element name)
H.it "CompareTo terms (when supported) pass through without change" $
QC.property $ \s -> checkDataAdapter
[TypeVariantLiteral, TypeVariantFunction]
compareStringsType
compareStringsType
False
(compareTo $ string s)
(compareTo $ string s)
H.it "Term terms (when supported) pass through without change" $
QC.property $ \() -> checkDataAdapter
[TypeVariantLiteral, TypeVariantFunction, TypeVariantElement]
int32ElementDataType
int32ElementDataType
False
delta
delta
H.it "Primitive function references (when supported) pass through without change" $
QC.property $ \name -> checkDataAdapter
[TypeVariantLiteral, TypeVariantFunction]
concatType
concatType
False
(primitive name)
(primitive name)
H.it "Projections (when supported) pass through without change" $
QC.property $ \fname -> checkDataAdapter
[TypeVariantLiteral, TypeVariantFunction, TypeVariantRecord]
exampleProjectionType
exampleProjectionType
False
(projection testTypePersonName fname)
(projection testTypePersonName fname)
H.it "Nominal types (when supported) pass through without change" $
QC.property $ \s -> checkDataAdapter
[TypeVariantLiteral, TypeVariantNominal]
stringAliasType
stringAliasType
False
(string s)
(string s)
unsupportedConstructorsAreModified :: H.SpecWith ()
unsupportedConstructorsAreModified = H.describe "Verify that unsupported term constructors are changed in the expected ways" $ do
H.it "Sets (when unsupported) become lists" $
QC.property $ \strings -> checkDataAdapter
[TypeVariantLiteral, TypeVariantList]
setOfStringsType
listOfStringsType
False
(stringSet strings)
(stringList $ S.toList strings)
H.it "Element references (when unsupported) become strings" $
QC.property $ \name@(Name nm) -> checkDataAdapter
[TypeVariantLiteral]
int32ElementType
Types.string
False
(element name)
(string nm) -- Note: the element name is not dereferenced
H.it "CompareTo terms (when unsupported) become variant terms" $
QC.property $ \s -> checkDataAdapter
[TypeVariantLiteral, TypeVariantUnion, TypeVariantRecord]
compareStringsType
(functionProxyType Types.string)
False
(compareTo $ string s)
(union functionProxyName $ field "compareTo" $ string s)
H.it "Data terms (when unsupported) become variant terms" $
QC.property $ \() -> checkDataAdapter
[TypeVariantLiteral, TypeVariantUnion, TypeVariantRecord]
int32ElementDataType
(functionProxyType Types.string)
False
delta
(union functionProxyName $ field "element" unit)
H.it "Optionals (when unsupported) become lists" $
QC.property $ \ms -> checkDataAdapter
[TypeVariantLiteral, TypeVariantList]
(Types.optional Types.string)
(Types.list Types.string)
False
(optional $ string <$> ms)
(list $ Y.maybe [] (\s -> [string s]) ms)
H.it "Primitive function references (when unsupported) become variant terms" $
QC.property $ \name -> checkDataAdapter
[TypeVariantLiteral, TypeVariantUnion, TypeVariantRecord]
concatType
(functionProxyType Types.string)
False
(primitive name)
(union functionProxyName $ field "primitive" $ string $ unName name) -- Note: the function name is not dereferenced
H.it "Projections (when unsupported) become variant terms" $
QC.property $ \fname -> checkDataAdapter
[TypeVariantLiteral, TypeVariantUnion, TypeVariantRecord]
exampleProjectionType
(functionProxyType testTypePerson)
False
(projection testTypePersonName fname)
(union functionProxyName $ field "record" $ string $
show (projection testTypePersonName fname :: Term Meta)) -- Note: the field name is not dereferenced
H.it "Nominal types (when unsupported) are dereferenced" $
QC.property $ \s -> checkDataAdapter
[TypeVariantLiteral, TypeVariantAnnotated]
stringAliasType
(TypeAnnotated $ Annotated Types.string $ Meta $
M.fromList [(metaDescription, Terms.string "An alias for the string type")])
False
(string s)
(string s)
H.it "Unions (when unsupported) become records" $
QC.property $ \i -> checkDataAdapter
[TypeVariantLiteral, TypeVariantOptional, TypeVariantRecord]
eitherStringOrInt8Type
(TypeRecord $ RowType eitherStringOrInt8TypeName Nothing [
Types.field "left" $ Types.optional Types.string,
Types.field "right" $ Types.optional Types.int16])
False
(union eitherStringOrInt8TypeName $ field "right" $ int8 i)
(record eitherStringOrInt8TypeName [
field "left" $ optional Nothing,
field "right" $ optional $ Just $ int16 $ fromIntegral i])
termsAreAdaptedRecursively :: H.SpecWith ()
termsAreAdaptedRecursively = H.describe "Verify that the adapter descends into subterms and transforms them appropriately" $ do
H.it "A list of int8's becomes a list of int32's" $
QC.property $ \ints -> checkDataAdapter
[TypeVariantLiteral, TypeVariantList]
listOfInt8sType
listOfInt16sType
False
(list $ int8 <$> ints)
(list $ int16 . fromIntegral <$> ints)
H.it "A list of sets of strings becomes a list of lists of strings" $
QC.property $ \lists -> checkDataAdapter
[TypeVariantLiteral, TypeVariantList]
listOfSetOfStringsType
listOfListsOfStringsType
False
(list $ (\l -> set $ S.fromList $ string <$> l) <$> lists)
(list $ (\l -> list $ string <$> S.toList (S.fromList l)) <$> lists)
H.it "A list of sets of element references becomes a list of lists of strings" $
QC.property $ \names -> checkDataAdapter
[TypeVariantLiteral, TypeVariantList]
listOfSetOfInt32ElementReferencesType
listOfListsOfStringsType
False
(list $ (\l -> set $ S.fromList $ element <$> l) <$> names)
(list $ (\l -> list $ string <$> S.toList (S.fromList $ unName <$> l)) <$> names)
roundTripsPreserveSelectedTypes :: H.SpecWith ()
roundTripsPreserveSelectedTypes = H.describe "Verify that the adapter is information preserving, i.e. that round-trips are no-ops" $ do
H.it "Check strings (pass-through)" $
QC.property $ \s -> roundTripIsNoop Types.string (string s)
H.it "Check lists (pass-through)" $
QC.property $ \strings -> roundTripIsNoop listOfStringsType (list $ string <$> strings)
H.it "Check sets (which map to lists)" $
QC.property $ \strings -> roundTripIsNoop setOfStringsType (stringSet strings)
H.it "Check element references (which map to strings)" $
QC.property $ \name -> roundTripIsNoop int32ElementType (element name)
H.it "Check compareTo terms (which map to variants)" $
QC.property $ \s -> roundTripIsNoop compareStringsType (compareTo $ string s)
H.it "Check data terms (which map to variants)" $
roundTripIsNoop int32ElementDataType delta
H.it "Check primitive function references (which map to variants)" $
QC.property $ \name -> roundTripIsNoop concatType (primitive name)
H.it "Check projection terms (which map to variants)" $
QC.property $ \fname -> roundTripIsNoop exampleProjectionType (projection testTypePersonName fname)
H.it "Check nominally typed terms (which pass through as instances of the aliased type)" $
QC.property $ \s -> roundTripIsNoop stringAliasType (string s)
roundTripsPreserveArbitraryTypes :: H.SpecWith ()
roundTripsPreserveArbitraryTypes = H.describe "Verify that the adapter is information preserving for arbitrary typed terms" $ do
H.it "Check arbitrary type/term pairs" $
QC.property $ \(TypedTerm typ term) -> roundTripIsNoop typ term
fieldAdaptersAreAsExpected :: H.SpecWith ()
fieldAdaptersAreAsExpected = H.describe "Check that field adapters are as expected" $ do
H.it "An int8 field becomes an int16 field" $
QC.property $ \i -> checkFieldAdapter
[TypeVariantLiteral, TypeVariantRecord]
(Types.field "second" Types.int8)
(Types.field "second" Types.int16)
False
(field "second" $ int8 i)
(field "second" $ int16 $ fromIntegral i)
roundTripIsNoop :: Type Meta -> Term Meta -> H.Expectation
roundTripIsNoop typ term = shouldSucceedWith
(step coderEncode term >>= step coderDecode)
term
where
step = adapt typ
-- Use a YAML-like language (but supporting unions) as the default target language
testLanguage = Language (LanguageName "hydra/test") $ LanguageConstraints {
languageConstraintsEliminationVariants = S.empty, -- S.fromList eliminationVariants,
languageConstraintsLiteralVariants = S.fromList [
LiteralVariantBoolean, LiteralVariantFloat, LiteralVariantInteger, LiteralVariantString],
languageConstraintsFloatTypes = S.fromList [FloatTypeBigfloat],
languageConstraintsFunctionVariants = S.empty,
languageConstraintsIntegerTypes = S.fromList [IntegerTypeBigint],
languageConstraintsTermVariants = S.fromList termVariants,
languageConstraintsTypeVariants = S.fromList [
TypeVariantAnnotated, TypeVariantLiteral, TypeVariantList, TypeVariantMap, TypeVariantRecord, TypeVariantUnion],
languageConstraintsTypes = \typ -> case stripType typ of
TypeOptional (TypeOptional _) -> False
_ -> True }
acx = AdapterContext testContext hydraCoreLanguage testLanguage
-- Note: in a real application, you wouldn't create the adapter just to use it once;
-- it should be created once, then applied to many terms.
adapt typ dir term = do
ad <- withState acx $ termAdapter typ
dir (adapterCoder ad) term
spec :: H.Spec
spec = do
constraintsAreAsExpected
supportedConstructorsAreUnchanged
unsupportedConstructorsAreModified
termsAreAdaptedRecursively
roundTripsPreserveSelectedTypes
roundTripsPreserveArbitraryTypes
fieldAdaptersAreAsExpected