diff --git a/.travis.yml b/.travis.yml index a6e5e9bda4..146dc5a632 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ before_install: - travis_retry sudo add-apt-repository -y ppa:hvr/ghc - travis_retry sudo apt-get update - travis_retry sudo apt-get install cabal-install-$CABALVER ghc-$GHCVER # see note about happy/alex - - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH + - export PATH=$HOME/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH install: - cabal --version @@ -39,6 +39,12 @@ script: - cabal check - cabal sdist # tests that a source-distribution can be generated + - cabal copy + - cd test/integration + - stack setup + - stack test + - cd ../.. + # Check that the resulting source distribution can be built & installed. # If there are no other `.tar.gz` files in `dist`, this can be even simpler: # `cabal install --force-reinstalls dist/*-*.tar.gz` diff --git a/src/test/IntegrationTests.hs b/src/test/IntegrationTests.hs deleted file mode 100644 index a95c305a60..0000000000 --- a/src/test/IntegrationTests.hs +++ /dev/null @@ -1,42 +0,0 @@ -import Control.Exception -import Test.Hspec -import System.Process (callProcess) -import System.Directory -import System.IO.Temp - -main :: IO () -main = hspec spec - -stack :: String -> [String] -> IO () -stack x xs = callProcess "stack" (x:xs) - - -inTempDir :: IO () -> IO () -inTempDir action = do - currentDirectory <- getCurrentDirectory - withSystemTempDirectory "stack-integration-tests" $ \tempDir -> do - let enterDir = setCurrentDirectory tempDir - let exitDir = setCurrentDirectory currentDirectory - bracket_ enterDir exitDir action - -inDir :: FilePath -> IO () -> IO () -inDir fp action = do - currentDirectory <- getCurrentDirectory - let enterDir = setCurrentDirectory fp - let exitDir = setCurrentDirectory currentDirectory - bracket_ enterDir exitDir action - - -spec :: Spec -spec = describe "stack" $ do - let textPkg = "text-1.2.1.1" - - describe "test" $ do - it "tests cyclic dependencies" $ inTempDir $ do - stack "unpack" [textPkg] - inDir textPkg $ stack "test" [] - - describe "unpack" $ do - it "does not create a stack.yaml" $ inTempDir $ do - stack "unpack" [textPkg] - doesFileExist "stack.yaml" `shouldReturn` False diff --git a/stack.cabal b/stack.cabal index 8316c0810f..fd272a9ebb 100644 --- a/stack.cabal +++ b/stack.cabal @@ -22,11 +22,6 @@ homepage: https://github.com/commercialhaskell/stack test/package-dump/ghc-7.8.txt test/package-dump/ghc-7.10.txt -flag integration-tests - manual: True - default: False - description: Build the integration test executable - library hs-source-dirs: src/ ghc-options: -Wall @@ -177,21 +172,6 @@ executable stack , http-client default-language: Haskell2010 -executable stack-integration-tests - hs-source-dirs: src/test - main-is: IntegrationTests.hs - ghc-options: -Wall - build-depends: base >= 4.7 && <5 - , hspec - , process - , temporary - , directory - default-language: Haskell2010 - if flag(integration-tests) - buildable: True - else - buildable: False - test-suite stack-test type: exitcode-stdio-1.0 hs-source-dirs: src/test diff --git a/test/integration/Dummy.hs b/test/integration/Dummy.hs new file mode 100644 index 0000000000..377b6b5516 --- /dev/null +++ b/test/integration/Dummy.hs @@ -0,0 +1,2 @@ +main :: IO () +main = return () diff --git a/test/integration/LICENSE b/test/integration/LICENSE new file mode 100644 index 0000000000..cee39cd0b9 --- /dev/null +++ b/test/integration/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2015, stack +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of stackage-common nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/test/integration/Spec.hs b/test/integration/Spec.hs new file mode 100644 index 0000000000..f4ebf4abaa --- /dev/null +++ b/test/integration/Spec.hs @@ -0,0 +1,128 @@ +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE ScopedTypeVariables #-} + +import Control.Applicative +import Control.Arrow +import Control.Concurrent.Async +import Control.Exception +import Control.Monad +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource +import qualified Data.ByteString.Lazy as L +import Data.Char +import Data.Conduit +import Data.Conduit.Binary (sinkLbs) +import Data.Conduit.Filesystem (sourceDirectoryDeep) +import qualified Data.Conduit.List as CL +import Data.Conduit.Process +import Data.List (isSuffixOf, stripPrefix) +import qualified Data.Map as Map +import Data.Text.Encoding.Error (lenientDecode) +import qualified Data.Text.Lazy as TL +import qualified Data.Text.Lazy.Encoding as TL +import Data.Typeable +import System.Directory +import System.Environment +import System.Exit +import System.FilePath +import System.IO.Temp +import System.PosixCompat.Files +import Test.Hspec + +main :: IO () +main = do + currDir <- getCurrentDirectory + + let findExe name = do + mexe <- findExecutable name + case mexe of + Nothing -> error $ name ++ " not found on PATH" + Just exe -> return exe + runghc <- findExe "runghc" + stack <- findExe "stack" + + tests <- getDirectoryContents "tests" >>= filterM hasTest + + envOrig <- getEnvironment + + withSystemTempDirectory ("stack-integration-home") $ \newHome -> do + let env' = Map.toList + $ Map.insert "STACK_EXE" stack + $ Map.insert "HOME" newHome + $ Map.insert "APPDATA" newHome + $ Map.delete "GHC_PACKAGE_PATH" + $ Map.fromList + $ map (first (map toUpper)) envOrig + + origStackRoot <- getAppUserDataDirectory "stack" + + hspec $ mapM_ (test runghc env' currDir origStackRoot newHome) tests + +hasTest :: FilePath -> IO Bool +hasTest dir = doesFileExist $ "tests" dir "Main.hs" + +test :: FilePath -- ^ runghc + -> [(String, String)] -- ^ env + -> FilePath -- ^ currdir + -> FilePath -- ^ origStackRoot + -> FilePath -- ^ newHome + -> String + -> Spec +test runghc env' currDir origStackRoot newHome name = it name $ withDir $ \dir -> do + removeDirectoryRecursive newHome + copyStackRoot origStackRoot (newHome takeFileName origStackRoot) + let testDir = currDir "tests" name + mainFile = testDir "Main.hs" + libDir = currDir "lib" + cp = (proc runghc + [ "-clear-package-db" + , "-global-package-db" + , "-i" ++ libDir + , mainFile + ]) + { cwd = Just dir + , env = Just env' + } + (ClosedStream, outSrc, errSrc, sph) <- streamingProcess cp + (out, err, ec) <- runConcurrently $ (,,) + <$> Concurrently (outSrc $$ sinkLbs) + <*> Concurrently (errSrc $$ sinkLbs) + <*> Concurrently (waitForStreamingProcess sph) + when (ec /= ExitSuccess) $ throwIO $ TestFailure out err ec + where + withDir = withSystemTempDirectory ("stack-integration-" ++ name) + +data TestFailure = TestFailure L.ByteString L.ByteString ExitCode + deriving Typeable +instance Show TestFailure where + show (TestFailure out err ec) = concat + [ "Exited with " ++ show ec + , "\n\nstdout:\n" + , toStr out + , "\n\nstderr:\n" + , toStr err + ] + where + toStr = TL.unpack . TL.decodeUtf8With lenientDecode +instance Exception TestFailure + +copyStackRoot :: FilePath -> FilePath -> IO () +copyStackRoot src dst = + runResourceT $ sourceDirectoryDeep False src $$ CL.mapM_ go + where + go srcfp = when toCopy $ liftIO $ do + Just suffix <- return $ stripPrefix src srcfp + let dstfp = dst ++ "/" ++ suffix + createDirectoryIfMissing True $ takeDirectory dstfp + createSymbolicLink srcfp dstfp `catch` \(_ :: IOException) -> + copyFile srcfp dstfp -- for Windows + where + toCopy = any (`isSuffixOf` srcfp) + -- FIXME command line parameters to control how many of these get + -- copied, trade-off of runtime/bandwidth vs isolation of tests + [ ".tar" + , ".xz" + -- , ".gz" + , ".7z.exe" + , "00-index.cache" + ] diff --git a/test/integration/lib/StackTest.hs b/test/integration/lib/StackTest.hs new file mode 100644 index 0000000000..786df660c1 --- /dev/null +++ b/test/integration/lib/StackTest.hs @@ -0,0 +1,43 @@ +module StackTest where + +import Control.Exception +import System.Environment +import System.FilePath +import System.Directory +import System.IO +import System.Process +import System.Exit +import System.Environment + +stack' :: [String] -> IO ExitCode +stack' args = do + stack <- getEnv "STACK_EXE" + putStrLn $ "Running stack with args " ++ show args + (Nothing, Nothing, Nothing, ph) <- createProcess (proc stack args) + waitForProcess ph + +stack :: [String] -> IO () +stack args = do + ec <- stack' args + if ec == ExitSuccess + then return () + else error $ "Exited with exit code: " ++ show ec + +stackErr :: [String] -> IO () +stackErr args = do + ec <- stack' args + if ec == ExitSuccess + then error "stack was supposed to fail, but didn't" + else return () + +doesNotExist :: FilePath -> IO () +doesNotExist fp = do + putStrLn $ "doesNotExist " ++ fp + isFile <- doesFileExist fp + if isFile + then error $ "File exists: " ++ fp + else do + isDir <- doesDirectoryExist fp + if isDir + then error $ "Directory exists: " ++ fp + else return () diff --git a/test/integration/stack-integration-tests.cabal b/test/integration/stack-integration-tests.cabal new file mode 100644 index 0000000000..03a82eb2cf --- /dev/null +++ b/test/integration/stack-integration-tests.cabal @@ -0,0 +1,45 @@ +name: stack-integration-tests +version: 0.0.0 +synopsis: Integration tests +description: Integration tests +license: BSD3 +license-file: LICENSE +author: Chris Done +maintainer: chrisdone@fpcomplete.com +category: Development +build-type: Simple +cabal-version: >=1.10 +homepage: https://github.com/commercialhaskell/stack + +-- This is only present because cabal won't allow a test suite without a +-- library or executable +executable stack-integration-dummy + main-is: Dummy.hs + build-depends: base + default-language: Haskell2010 + +test-suite stack-integration-tests + type: exitcode-stdio-1.0 + main-is: Spec.hs + ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N + + build-depends: base >= 4.7 && < 10 + , temporary + , hspec + , process + , filepath + , directory + , text + , unix-compat + , containers + , conduit + , conduit-extra + , resourcet + , async + , transformers + , bytestring + default-language: Haskell2010 + +source-repository head + type: git + location: https://github.com/commercialhaskell/stack diff --git a/test/integration/stack.yaml b/test/integration/stack.yaml new file mode 100644 index 0000000000..ca77b50114 --- /dev/null +++ b/test/integration/stack.yaml @@ -0,0 +1,3 @@ +packages: +- '.' +resolver: lts-2.9 diff --git a/test/integration/tests/basic-install/Main.hs b/test/integration/tests/basic-install/Main.hs new file mode 100644 index 0000000000..a638f19f4a --- /dev/null +++ b/test/integration/tests/basic-install/Main.hs @@ -0,0 +1,6 @@ +import StackTest + +main :: IO () +main = do + stack ["install", "acme-missiles-0.3"] + doesNotExist "stack.yaml" diff --git a/test/integration/tests/cyclic-test-deps/Main.hs b/test/integration/tests/cyclic-test-deps/Main.hs new file mode 100644 index 0000000000..1f3d050e71 --- /dev/null +++ b/test/integration/tests/cyclic-test-deps/Main.hs @@ -0,0 +1,7 @@ +import StackTest + +main :: IO () +main = do + stack ["unpack", "text-1.2.1.1"] + stack ["init", "--resolver", "lts-2.9"] + stack ["test", "--dry-run"] diff --git a/test/integration/tests/sanity/Main.hs b/test/integration/tests/sanity/Main.hs new file mode 100644 index 0000000000..abbadbda91 --- /dev/null +++ b/test/integration/tests/sanity/Main.hs @@ -0,0 +1,12 @@ +import StackTest + +main :: IO () +main = do + stack ["--version"] + stack ["--help"] + stack ["unpack", "acme-missiles-0.2"] + stack ["unpack", "acme-missiles"] + stackErr ["command-does-not-exist"] + stackErr ["unpack", "invalid-package-name-"] + stackErr ["build"] + doesNotExist "stack.yaml"