{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} module Stack.PackageDumpSpec where import Conduit import qualified Data.Conduit.List as CL import Data.Conduit.Text (decodeUtf8) import qualified Data.Map as Map import qualified Data.Set as Set import Distribution.License (License(..)) import Distribution.Types.PackageName (mkPackageName) import Distribution.Version (mkVersion) import Path (parseAbsFile) import Stack.PackageDump import Stack.Prelude import Stack.Types.Config import Stack.Types.GhcPkgId import RIO.Process import Test.Hspec import Test.Hspec.QuickCheck main :: IO () main = hspec spec spec :: Spec spec = do describe "eachSection" $ do let test name content expected = it name $ do actual <- runConduit $ yield content .| eachSection CL.consume .| CL.consume actual `shouldBe` expected test "unix line endings" "foo\nbar\n---\nbaz---\nbin\n---\n" [ ["foo", "bar"] , ["baz---", "bin"] ] test "windows line endings" "foo\r\nbar\r\n---\r\nbaz---\r\nbin\r\n---\r\n" [ ["foo", "bar"] , ["baz---", "bin"] ] it "eachPair" $ do let bss = [ "key1: val1" , "key2: val2a" , " val2b" , "key3:" , "key4:" , " val4a" , " val4b" ] sink k = fmap (k, ) CL.consume actual <- runConduit $ mapM_ yield bss .| eachPair sink .| CL.consume actual `shouldBe` [ ("key1", ["val1"]) , ("key2", ["val2a", "val2b"]) , ("key3", []) , ("key4", ["val4a", "val4b"]) ] describe "conduitDumpPackage" $ do it "ghc 7.8" $ do haskell2010:_ <- withSourceFile "test/package-dump/ghc-7.8.txt" $ \src -> runConduit $ src .| decodeUtf8 .| conduitDumpPackage .| CL.consume ghcPkgId <- parseGhcPkgId "haskell2010-1.1.2.0-05c8dd51009e08c6371c82972d40f55a" packageIdent <- maybe (fail "Not parsable package id") return $ parsePackageIdentifier "haskell2010-1.1.2.0" depends <- mapM parseGhcPkgId [ "array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b" , "base-4.7.0.2-bfd89587617e381ae01b8dd7b6c7f1c1" , "ghc-prim-0.3.1.0-a24f9c14c632d75b683d0f93283aea37" ] haskell2010 { dpExposedModules = mempty } `shouldBe` DumpPackage { dpGhcPkgId = ghcPkgId , dpPackageIdent = packageIdent , dpParentLibIdent = Nothing , dpLicense = Just BSD3 , dpLibDirs = ["/opt/ghc/7.8.4/lib/ghc-7.8.4/haskell2010-1.1.2.0"] , dpDepends = depends , dpLibraries = ["HShaskell2010-1.1.2.0"] , dpHasExposedModules = True , dpHaddockInterfaces = ["/opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskell2010-1.1.2.0/haskell2010.haddock"] , dpHaddockHtml = Just "/opt/ghc/7.8.4/share/doc/ghc/html/libraries/haskell2010-1.1.2.0" , dpIsExposed = False , dpExposedModules = mempty } it "ghc 7.10" $ do haskell2010:_ <- withSourceFile "test/package-dump/ghc-7.10.txt" $ \src -> runConduit $ src .| decodeUtf8 .| conduitDumpPackage .| CL.consume ghcPkgId <- parseGhcPkgId "ghc-7.10.1-325809317787a897b7a97d646ceaa3a3" pkgIdent <- maybe (fail "Not parsable package id") return $ parsePackageIdentifier "ghc-7.10.1" depends <- mapM parseGhcPkgId [ "array-0.5.1.0-e29cdbe82692341ebb7ce6e2798294f9" , "base-4.8.0.0-1b689eb8d72c4d4cc88f445839c1f01a" , "bin-package-db-0.0.0.0-708fc7d634a370b311371a5bcde40b62" , "bytestring-0.10.6.0-0909f8f31271f3d75749190bf2ee35db" , "containers-0.5.6.2-2114032c163425cc264e6e1169dc2f6d" , "directory-1.2.2.0-b4959b472d9eee380c6b32291ade29e0" , "filepath-1.4.0.0-40d643aa87258c186441a1f8f3e13ca6" , "hoopl-3.10.0.2-8c8dfc4c3140e5f7c982da224c3cb1f0" , "hpc-0.6.0.2-ac9064885aa8cb08a93314222939ead4" , "process-1.2.3.0-3b1e9bca6ac38225806ff7bbf3f845b1" , "template-haskell-2.10.0.0-e895139a0ffff267d412e3d0191ce93b" , "time-1.5.0.1-e17a9220d438435579d2914e90774246" , "transformers-0.4.2.0-c1a7bb855a176fe475d7b665301cd48f" , "unix-2.7.1.0-e5915eb989e568b732bc7286b0d0817f" ] haskell2010 { dpExposedModules = mempty } `shouldBe` DumpPackage { dpGhcPkgId = ghcPkgId , dpPackageIdent = pkgIdent , dpParentLibIdent = Nothing , dpLicense = Just BSD3 , dpLibDirs = ["/opt/ghc/7.10.1/lib/ghc-7.10.1/ghc_EMlWrQ42XY0BNVbSrKixqY"] , dpHaddockInterfaces = ["/opt/ghc/7.10.1/share/doc/ghc/html/libraries/ghc-7.10.1/ghc.haddock"] , dpHaddockHtml = Just "/opt/ghc/7.10.1/share/doc/ghc/html/libraries/ghc-7.10.1" , dpDepends = depends , dpLibraries = ["HSghc-7.10.1-EMlWrQ42XY0BNVbSrKixqY"] , dpHasExposedModules = True , dpIsExposed = False , dpExposedModules = mempty } it "ghc 7.8.4 (osx)" $ do hmatrix:_ <- withSourceFile "test/package-dump/ghc-7.8.4-osx.txt" $ \src -> runConduit $ src .| decodeUtf8 .| conduitDumpPackage .| CL.consume ghcPkgId <- parseGhcPkgId "hmatrix-0.16.1.5-12d5d21f26aa98774cdd8edbc343fbfe" pkgId <- maybe (fail "Not parsable package id") return $ parsePackageIdentifier "hmatrix-0.16.1.5" depends <- mapM parseGhcPkgId [ "array-0.5.0.0-470385a50d2b78598af85cfe9d988e1b" , "base-4.7.0.2-918c7ac27f65a87103264a9f51652d63" , "binary-0.7.1.0-108d06eea2ef05e517f9c1facf10f63c" , "bytestring-0.10.4.0-78bc8f2c724c765c78c004a84acf6cc3" , "deepseq-1.3.0.2-0ddc77716bd2515426e1ba39f6788a4f" , "random-1.1-822c19b7507b6ac1aaa4c66731e775ae" , "split-0.2.2-34cfb851cc3784e22bfae7a7bddda9c5" , "storable-complex-0.2.2-e962c368d58acc1f5b41d41edc93da72" , "vector-0.10.12.3-f4222db607fd5fdd7545d3e82419b307"] hmatrix `shouldBe` DumpPackage { dpGhcPkgId = ghcPkgId , dpPackageIdent = pkgId , dpParentLibIdent = Nothing , dpLicense = Just BSD3 , dpLibDirs = [ "/Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/lib/x86_64-osx-ghc-7.8.4/hmatrix-0.16.1.5" , "/opt/local/lib/" , "/usr/local/lib/" , "C:/Program Files/Example/"] , dpHaddockInterfaces = ["/Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/doc/html/hmatrix.haddock"] , dpHaddockHtml = Just "/Users/alexbiehl/.stack/snapshots/x86_64-osx/lts-2.13/7.8.4/doc/html" , dpDepends = depends , dpLibraries = ["HShmatrix-0.16.1.5"] , dpHasExposedModules = True , dpIsExposed = True , dpExposedModules = Set.fromList ["Data.Packed","Data.Packed.Vector","Data.Packed.Matrix","Data.Packed.Foreign","Data.Packed.ST","Data.Packed.Development","Numeric.LinearAlgebra","Numeric.LinearAlgebra.LAPACK","Numeric.LinearAlgebra.Algorithms","Numeric.Container","Numeric.LinearAlgebra.Util","Numeric.LinearAlgebra.Devel","Numeric.LinearAlgebra.Data","Numeric.LinearAlgebra.HMatrix","Numeric.LinearAlgebra.Static"] } it "ghc HEAD" $ do ghcBoot:_ <- withSourceFile "test/package-dump/ghc-head.txt" $ \src -> runConduit $ src .| decodeUtf8 .| conduitDumpPackage .| CL.consume ghcPkgId <- parseGhcPkgId "ghc-boot-0.0.0.0" pkgId <- maybe (fail "Not parsable package id") return $ parsePackageIdentifier "ghc-boot-0.0.0.0" depends <- mapM parseGhcPkgId [ "base-4.9.0.0" , "binary-0.7.5.0" , "bytestring-0.10.7.0" , "directory-1.2.5.0" , "filepath-1.4.1.0" ] ghcBoot `shouldBe` DumpPackage { dpGhcPkgId = ghcPkgId , dpPackageIdent = pkgId , dpParentLibIdent = Nothing , dpLicense = Just BSD3 , dpLibDirs = ["/opt/ghc/head/lib/ghc-7.11.20151213/ghc-boot-0.0.0.0"] , dpHaddockInterfaces = ["/opt/ghc/head/share/doc/ghc/html/libraries/ghc-boot-0.0.0.0/ghc-boot.haddock"] , dpHaddockHtml = Just "/opt/ghc/head/share/doc/ghc/html/libraries/ghc-boot-0.0.0.0" , dpDepends = depends , dpLibraries = ["HSghc-boot-0.0.0.0"] , dpHasExposedModules = True , dpIsExposed = True , dpExposedModules = Set.fromList ["GHC.Lexeme", "GHC.PackageDb"] } it "sinkMatching" $ runEnvNoLogging $ \pkgexe -> do m <- ghcPkgDump pkgexe [] $ conduitDumpPackage .| sinkMatching (Map.singleton (mkPackageName "transformers") (mkVersion [0, 0, 0, 0, 0, 0, 1])) case Map.lookup (mkPackageName "base") m of Nothing -> error "base not present" Just _ -> return () liftIO $ do Map.lookup (mkPackageName "transformers") m `shouldBe` Nothing Map.lookup (mkPackageName "ghc") m `shouldBe` Nothing describe "pruneDeps" $ do it "sanity check" $ do let prunes = [ ((1, 'a'), []) , ((1, 'b'), []) , ((2, 'a'), [(1, 'b')]) , ((2, 'b'), [(1, 'a')]) , ((3, 'a'), [(1, 'c')]) , ((4, 'a'), [(2, 'a')]) ] actual = fst <$> pruneDeps fst fst snd bestPrune prunes actual `shouldBe` Map.fromList [ (1, (1, 'b')) , (2, (2, 'a')) , (4, (4, 'a')) ] prop "invariant holds" $ \prunes' -> -- Force uniqueness let prunes = Map.toList $ Map.fromList prunes' in checkDepsPresent prunes $ fst <$> pruneDeps fst fst snd bestPrune prunes type PruneCheck = ((Int, Char), [(Int, Char)]) bestPrune :: PruneCheck -> PruneCheck -> PruneCheck bestPrune x y | fst x > fst y = x | otherwise = y checkDepsPresent :: [PruneCheck] -> Map Int (Int, Char) -> Bool checkDepsPresent prunes selected = all hasDeps $ Set.toList allIds where depMap = Map.fromList prunes allIds = Set.fromList $ Map.elems selected hasDeps ident = case Map.lookup ident depMap of Nothing -> error "checkDepsPresent: missing in depMap" Just deps -> Set.null $ Set.difference (Set.fromList deps) allIds runEnvNoLogging :: (GhcPkgExe -> RIO LoggedProcessContext a) -> IO a runEnvNoLogging inner = do envVars <- view envVarsL <$> mkDefaultProcessContext menv <- mkProcessContext $ Map.delete "GHC_PACKAGE_PATH" envVars let find name = runRIO menv (findExecutable name) >>= either throwIO parseAbsFile pkg <- GhcPkgExe <$> find "ghc-pkg" runRIO (LoggedProcessContext menv mempty) (inner pkg)