Cabal-3.0.0.0: tests/UnitTests/Distribution/SPDX.hs
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-deprecations #-}
module UnitTests.Distribution.SPDX (spdxTests) where
import Distribution.Compat.Prelude.Internal
import Prelude ()
import Distribution.SPDX
import Distribution.Parsec (eitherParsec)
import Distribution.Pretty (prettyShow)
import Test.Tasty
import Test.Tasty.QuickCheck
#if MIN_VERSION_binary(0,7,0)
import qualified Data.Binary as Binary
import qualified Data.Binary.Get as Binary
import qualified Data.Binary.Put as Binary
import qualified Data.ByteString.Lazy as LBS
import GHC.Generics (to, from)
#endif
spdxTests :: [TestTree]
spdxTests =
[ testProperty "LicenseId roundtrip" licenseIdRoundtrip
#if MIN_VERSION_binary(0,7,0)
, testProperty "LicenseId Binary.put" licenseIdBinaryPut
, testProperty "LicenseId Binary.get" licenseIdBinaryGet
#endif
, testProperty "LicenseExceptionId roundtrip" licenseExceptionIdRoundtrip
#if MIN_VERSION_binary(0,7,0)
, testProperty "LicenseExceptionId Binary.put" licenseExceptionIdBinaryPut
, testProperty "LicenseExceptionId Binary.get" licenseExceptionIdBinaryGet
#endif
, testProperty "LicenseRef roundtrip" licenseRefRoundtrip
, testProperty "SimpleLicenseExpression roundtrip" simpleLicenseExpressionRoundtrip
, testProperty "LicenseExpression roundtrip" licenseExpressionRoundtrip
, testProperty "isAcceptableLicense l = True" shouldAcceptProp
, testProperty "isAcceptableLicense l = False" shouldRejectProp
]
licenseIdRoundtrip :: LicenseId -> Property
licenseIdRoundtrip x =
counterexample (prettyShow x) $
Right x === eitherParsec (prettyShow x)
#if MIN_VERSION_binary(0,7,0)
licenseIdBinaryPut :: LicenseId -> Property
licenseIdBinaryPut x =
Binary.runPut (Binary.put x)
===
Binary.runPut (Binary.gput (from x))
licenseIdBinaryGet :: Word8 -> Word8 -> Property
licenseIdBinaryGet w0 w1 =
stripMsg id (Binary.runGetOrFail Binary.get bs)
===
stripMsg to (Binary.runGetOrFail Binary.gget bs)
where
bs = LBS.pack [w0, w1]
stripMsg
:: (a -> LicenseId)
-> Either (x, y, String) (x, y, a)
-> Either (x, y) (x, y, LicenseId)
stripMsg _ (Left (x,y,_)) = Left (x,y)
stripMsg f (Right (x,y,t)) = Right (x,y,f t)
#endif
licenseExceptionIdRoundtrip :: LicenseExceptionId -> Property
licenseExceptionIdRoundtrip x =
counterexample (prettyShow x) $
Right x === eitherParsec (prettyShow x)
#if MIN_VERSION_binary(0,7,0)
licenseExceptionIdBinaryPut :: LicenseExceptionId -> Property
licenseExceptionIdBinaryPut x =
Binary.runPut (Binary.put x)
===
Binary.runPut (Binary.gput (from x))
licenseExceptionIdBinaryGet :: Word8 -> Property
licenseExceptionIdBinaryGet w0 =
stripMsg id (Binary.runGetOrFail Binary.get bs)
===
stripMsg to (Binary.runGetOrFail Binary.gget bs)
where
bs = LBS.pack [w0]
stripMsg
:: (a -> LicenseExceptionId)
-> Either (x, y, String) (x, y, a)
-> Either (x, y) (x, y, LicenseExceptionId)
stripMsg _ (Left (x,y,_)) = Left (x,y)
stripMsg f (Right (x,y,t)) = Right (x,y,f t)
#endif
licenseRefRoundtrip :: LicenseRef -> Property
licenseRefRoundtrip x =
counterexample (prettyShow x) $
Right x === eitherParsec (prettyShow x)
simpleLicenseExpressionRoundtrip :: SimpleLicenseExpression -> Property
simpleLicenseExpressionRoundtrip x =
counterexample (prettyShow x) $
Right x === eitherParsec (prettyShow x)
licenseExpressionRoundtrip :: LicenseExpression -> Property
licenseExpressionRoundtrip x =
counterexample (prettyShow x) $
Right (reassoc x) === eitherParsec (prettyShow x)
-- Parser produces right biased trees of and/or expressions
reassoc :: LicenseExpression -> LicenseExpression
reassoc (EOr a b) = case reassoc a of
EOr x y -> EOr x (reassoc (EOr y b))
x -> EOr x (reassoc b)
reassoc (EAnd a b) = case reassoc a of
EAnd x y -> EAnd x (reassoc (EAnd y b))
x -> EAnd x (reassoc b)
reassoc l = l
-------------------------------------------------------------------------------
-- isAcceptableLicence
-------------------------------------------------------------------------------
shouldAccept :: [License]
shouldAccept = map License
[ simpleLicenseExpression GPL_2_0_only
, simpleLicenseExpression GPL_2_0_or_later
, simpleLicenseExpression BSD_2_Clause
, simpleLicenseExpression BSD_3_Clause
, simpleLicenseExpression MIT
, simpleLicenseExpression ISC
, simpleLicenseExpression MPL_2_0
, simpleLicenseExpression Apache_2_0
, simpleLicenseExpression CC0_1_0
, simpleLicenseExpression BSD_4_Clause `EOr` simpleLicenseExpression MIT
]
shouldReject :: [License]
shouldReject = map License
[ simpleLicenseExpression BSD_4_Clause
, simpleLicenseExpression BSD_4_Clause `EAnd` simpleLicenseExpression MIT
]
-- | A sketch of what Hackage could accept
--
-- * NONE is rejected
--
-- * "or later" syntax (+ postfix) is rejected
--
-- * "WITH exc" exceptions are rejected
--
-- * There should be a way to interpert license as (conjunction of)
-- OSI-accepted licenses or CC0
--
isAcceptableLicense :: License -> Bool
isAcceptableLicense NONE = False
isAcceptableLicense (License expr) = goExpr expr
where
goExpr (EAnd a b) = goExpr a && goExpr b
goExpr (EOr a b) = goExpr a || goExpr b
goExpr (ELicense _ (Just _)) = False -- Don't allow exceptions
goExpr (ELicense s Nothing) = goSimple s
goSimple (ELicenseRef _) = False -- don't allow referenced licenses
goSimple (ELicenseIdPlus _) = False -- don't allow + licenses (use GPL-3.0-or-later e.g.)
goSimple (ELicenseId CC0_1_0) = True -- CC0 isn't OSI approved, but we allow it as "PublicDomain", this is eg. PublicDomain in http://hackage.haskell.org/package/string-qq-0.0.2/src/LICENSE
goSimple (ELicenseId lid) = licenseIsOsiApproved lid -- allow only OSI approved licenses.
shouldAcceptProp :: Property
shouldAcceptProp = conjoin $
map (\l -> counterexample (prettyShow l) (isAcceptableLicense l)) shouldAccept
shouldRejectProp :: Property
shouldRejectProp = conjoin $
map (\l -> counterexample (prettyShow l) (not $ isAcceptableLicense l)) shouldReject
-------------------------------------------------------------------------------
-- Instances
-------------------------------------------------------------------------------
instance Arbitrary LicenseId where
arbitrary = elements $ licenseIdList LicenseListVersion_3_6
instance Arbitrary LicenseExceptionId where
arbitrary = elements $ licenseExceptionIdList LicenseListVersion_3_6
instance Arbitrary LicenseRef where
arbitrary = mkLicenseRef' <$> ids' <*> ids
where
ids = listOf1 $ elements $ ['a'..'z'] ++ ['A' .. 'Z'] ++ ['0'..'9'] ++ "_-"
ids' = oneof [ pure Nothing, Just <$> ids ]
instance Arbitrary SimpleLicenseExpression where
arbitrary = oneof
[ ELicenseId <$> arbitrary
, ELicenseIdPlus <$> arbitrary
, ELicenseRef <$> arbitrary
]
instance Arbitrary LicenseExpression where
arbitrary = sized arb
where
arb n
| n <= 0 = ELicense <$> arbitrary <*> pure Nothing
| otherwise = oneof
[ ELicense <$> arbitrary <*> arbitrary
, EAnd <$> arbA <*> arbB
, EOr <$> arbA <*> arbB
]
where
m = n `div` 2
arbA = arb m
arbB = arb (n - m)
shrink (EAnd a b) = a : b : map (uncurry EAnd) (shrink (a, b))
shrink (EOr a b) = a : b : map (uncurry EOr) (shrink (a, b))
shrink _ = []