diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,19 @@
+## 0.2.0.0
+
+* Bug fixes
+* Allow an Upload to store contents of file to be uploaded.
+* Support older versions of directory and http-client
+* Allow extra args to be passed to cabal when configuring or when
+  running haddock.
+* Added hspec and docstring tests
+* Now running travis CI on a range of platforms and ghc versions.
+
+## 0.3.0.0
+
+* Bug fixes
+* Updated documentation, added a "quick usage" section and troubleshooting
+* `hup` now checks whether `cabal` is in the package snapshot binaries
+  directory, and installs it if not.
+* Added `packbuild` and `packboth` commands.
+* Added a program test to .travis.yml
+
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,19 +1,29 @@
-# hup [![Hackage version](https://img.shields.io/hackage/v/hup.svg?label=Hackage)](https://hackage.haskell.org/package/hup) [![Linux Build Status](https://img.shields.io/travis/phlummox/hup.svg?label=Linux%20build)](https://travis-ci.org/phlummox/hup) [![Windows Build Status](https://img.shields.io/appveyor/ci/phlummox/hup.svg?label=Windows%20build)](https://ci.appveyor.com/project/phlummox/hup)
+# hup [![Hackage version](https://img.shields.io/hackage/v/hup.svg?label=Hackage)](https://hackage.haskell.org/package/hup) [![Linux Build Status](https://img.shields.io/travis/phlummox/hup.svg?label=Linux%20build)](https://travis-ci.org/phlummox/hup) 
 
 Small program for building and uploading packages and documentation
 built with `stack` to a hackage server; a Haskellified version of
 [phadej's script](https://github.com/phadej/binary-orphans/blob/master/hackage-docs.sh),
 which is a stack-enabled version of [ekmett's script](https://github.com/ekmett/lens/blob/master/scripts/hackage-docs.sh).
 
-In addition to `stack`, requires that `cabal` and `haddock` are on your path.
-(If you're using stack, they're easily installed with, e.g.  `stack install
-cabal-install`.)
+In addition to `stack`, it requires the `cabal` executable
+(but will install an appropriate `cabal` if it doesn't find one in the
+binaries for the package snapshot your project is using).
 
 ## Installation
 
-Install in the standard Haskell way: `cabal install hup`, or `stack
-install hup`.
+Install in the standard Stack way with `stack install hup`.
 
+## Quick usage
+
+Try:
+
+~~~ 
+$ cd /path/to/my/project
+$ stack build
+$ hup packboth -u myHackageUserID -p myHackagePassword
+$ hup docboth -u myHackageUserID -p myHackagePassword 
+~~~
+
 ## Usage
 
 * `hup [COMMAND] ... [OPTIONS]`
@@ -95,6 +105,28 @@
         -c --candidate             
         -u --user=USER             
         -p --password=PASSWORD      
+
+## Troubleshooting
+
+### I get an error during upload that says "...: does not exist (no such protocol name: tcp)"
+
+This is not actually a bug in `hup`, but is found in e.g. Docker containers
+that don't have all the packages needed for networking - see e.g. 
+[here](https://stackoverflow.com/questions/46322773/yesod-app-in-docker-container-cant-make-network-requests) on StackOverflow.
+
+You will need to install networking packages appropriate for your distro - on Ubuntu, something like ca-certificates, libgnutls28 (or another version of the GNU TLS library), and netbase.  
+
+### I get some sort of error when building documents that says "...haddock: internal error: ... hGetContents: invalid argument (invalid byte sequence)"
+
+Again, this isn't actually a bug in `hup`, but happens (e.g. in Docker
+containers) when the system locale is not properly set up (see a bug report
+[here](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=871839) arising from a
+similar issue). Annoyingly, `haddock` depends on the locale being properly set,
+though it doesn't really seem necessary.
+
+Try running `locale-gen "en_US.UTF-8"` to generate an appropriate UTF-8
+locale, and `export LC_ALL="en_US.UTF-8"` so that the locale can be found
+from environment variables. 
 
 ## Bash command-line completion
 
diff --git a/hup.cabal b/hup.cabal
--- a/hup.cabal
+++ b/hup.cabal
@@ -1,5 +1,5 @@
 name:                hup
-version:             0.2.0.0
+version:             0.3.0.0
 synopsis:            Upload packages or documentation to a hackage server
 description:         
   Upload packages or documentation to a hackage server
@@ -15,7 +15,7 @@
 build-type:          Simple
 tested-with:         GHC == 8.0.1, GHC == 7.10.3 
 cabal-version:       >=1.10
-extra-source-files:  README.md, stack.yaml 
+extra-source-files:  README.md, stack.yaml, ChangeLog.md 
 
 Flag EnableWebTests
   Description: Enable tests that do a (pretty minimal) check by running an actual
@@ -67,6 +67,8 @@
     , SanityCheck
     , Types
     , Upload
+    , Stack
+    , DocBuilding
   if flag(PatchHelpMessage)
     other-modules: CmdArgs.PatchHelp
     cpp-options:   -DPATCH_HELP
@@ -79,16 +81,16 @@
     , bytestring
     , directory 
     , mtl
-    , shelly >= 1.6.5
-    -- shelly - higher possibly needed?
+    , shelly >= 1.6.6
     , tagsoup
+    -- , temporary >= 1.2.1
     , text
     , transformers
     , hup
 
 test-suite hup-spec
   type:                exitcode-stdio-1.0
-  hs-source-dirs:      test
+  hs-source-dirs:      src-hspec-test
   main-is:             Spec.hs
   build-depends:
       base
@@ -99,6 +101,7 @@
     , http-types
     , hup
     , hspec
+    , hspec-core
     , QuickCheck
     , simple 
     , temporary
@@ -128,7 +131,7 @@
 
 test-suite hup-doctest
   type:                exitcode-stdio-1.0
-  hs-source-dirs:      test
+  hs-source-dirs:      src-doctest
   main-is:             DocTest.hs
   build-depends:       base
                      , hup
diff --git a/lib/Distribution/Hup.hs b/lib/Distribution/Hup.hs
--- a/lib/Distribution/Hup.hs
+++ b/lib/Distribution/Hup.hs
@@ -42,6 +42,5 @@
 
 import Distribution.Hup.BuildTar 
 import Distribution.Hup.Parse
-import Distribution.Hup.Types
 import Distribution.Hup.Upload
 
diff --git a/lib/Distribution/Hup/Parse.hs b/lib/Distribution/Hup/Parse.hs
--- a/lib/Distribution/Hup/Parse.hs
+++ b/lib/Distribution/Hup/Parse.hs
@@ -12,7 +12,6 @@
 
 import Control.Monad.Except       (MonadError(..),when)
 
-import Data.ByteString.Lazy.Char8 (pack)
 import Data.Char                  (isDigit, toLower, isSpace)
 import Data.List                  (dropWhileEnd,isSuffixOf,stripPrefix
                                   ,intercalate)
@@ -122,7 +121,7 @@
 extractCabal find = f . words . replace ":" " : "
     where
         f (name:":":val:_) | map toLower find == map toLower name = val
-        f (x:xs) = f xs
+        f (_x:xs) = f xs
         f [] = error "Failed to find the Cabal key " ++ find
 
 -- | Inspect the name of a .tar.gz file to work out the package name 
@@ -136,9 +135,9 @@
   (base, ext) <- return $ splitExtension base 
   ext `shouldBe` ".tar"
   base        <- return $ snd $ splitFileName base
-  let isDocco = if "-docs" `isSuffixOf` base
-                then IsDocumentation 
-                else IsPackage
+  --let isDocco = if "-docs" `isSuffixOf` base
+  --              then IsDocumentation 
+  --              else IsPackage
   (base, isDocco) <- return $ if "-docs" `isSuffixOf` base
                               then let base' = intercalate "-" $ 
                                                init $ splitOn "-" base
diff --git a/lib/Distribution/Hup/Upload.hs b/lib/Distribution/Hup/Upload.hs
--- a/lib/Distribution/Hup/Upload.hs
+++ b/lib/Distribution/Hup/Upload.hs
@@ -22,10 +22,9 @@
 import qualified Data.ByteString.Builder as Bu
 import Data.List                        (dropWhileEnd)
 import Data.Maybe                       (fromJust)
-import Data.ByteString.Char8            (pack,unpack,putStrLn,ByteString(..) )
+import Data.ByteString.Char8            (pack,ByteString )
 import qualified Data.ByteString.Lazy.Char8 as LBS 
 import qualified Data.ByteString.Lazy as L (ByteString)
-import qualified Data.ByteString as BS 
 import Data.Monoid                      ( (<>) )
 import qualified Network.HTTP.Client as C
 import Network.HTTP.Client              (requestHeaders, Request, RequestBody(..)
@@ -40,6 +39,7 @@
 import Distribution.Hup.Types 
 -- for re-export
 
+import Control.Exception
 
 
 #if MIN_VERSION_http_client(0,4,30)
@@ -156,15 +156,19 @@
           let url = getUploadUrl serverUrl upl
           putDocs url filePath fileConts userAuth
 
--- | Send an HTTP request and get the response.
--- 
--- May throw an exception on network errors etc., but not on
--- a non-2XX response (e.g. a 404).
-sendRequest
-  :: Request -> IO Response
-sendRequest req = do
-  man <- C.newManager tlsManagerSettings
-  mkResponse <$> C.httpLbs req man
+-- | Send an HTTP request and get the response (or an exception)
+sendRequest :: Request -> IO (Either C.HttpException Response) 
+sendRequest req = 
+  do
+    man <- C.newManager tlsManagerSettings
+    tryHttp (mkResponse <$> C.httpLbs req man) 
+  where
+    --idHttp :: (C.HttpException -> IO a) -> (C.HttpException -> IO a) 
+    --idHttp = id
+
+    tryHttp ::  IO a -> IO (Either C.HttpException a) 
+    tryHttp = try
+
 
 
 
diff --git a/src-doctest/DocTest.hs b/src-doctest/DocTest.hs
new file mode 100644
--- /dev/null
+++ b/src-doctest/DocTest.hs
@@ -0,0 +1,7 @@
+
+import System.FilePath.Glob (glob)
+import Test.DocTest (doctest)
+
+main :: IO ()
+main = glob "lib/**/*.hs" >>= doctest
+
diff --git a/src-hspec-test/Distribution/Hup/Parse/Test.hs b/src-hspec-test/Distribution/Hup/Parse/Test.hs
new file mode 100644
--- /dev/null
+++ b/src-hspec-test/Distribution/Hup/Parse/Test.hs
@@ -0,0 +1,64 @@
+
+
+{- |
+
+Support code for testing Distribution.Hup.Parse 
+
+-}
+module Distribution.Hup.Parse.Test where
+
+import Data.List                  (intercalate)
+import Data.ByteString.Lazy.Char8 (pack)
+import Test.QuickCheck
+
+import Distribution.Hup.Parse
+
+
+arbWord = do
+  len <- choose (1, 10)
+  vectorOf len $
+      oneof [choose ('a', 'z')
+           ,choose ('A', 'Z')]
+
+
+arbName :: Gen String
+arbName = do
+  len <- choose (1, 4)
+  intercalate "-" <$> vectorOf len arbWord
+
+arbVersion = do
+  numComponents <- choose (1,10)
+  numbers <- vectorOf numComponents $ getNonNegative <$> 
+                                      (arbitrary :: Gen (NonNegative Int))
+  return $ intercalate "." $ map show numbers 
+
+-- Generate an Upload, including an empty file contents.
+arbUpload :: Gen Upload
+arbUpload = do
+  name   <- arbName
+  ver    <- arbVersion
+  isPack <- elements [IsPackage, IsDocumentation]
+  isCand <- elements [NormalPkg, CandidatePkg]
+  let pk   = Package name ver
+      file = name ++ "-" ++ ver ++ 
+                if isPack == IsPackage
+                then ".tar.gz"
+                else "-docs.tar.gz"
+  return $ Upload pk file (Just $ pack "") isPack isCand
+
+prop_parseTgzFilename_roundtripsOK  = 
+  forAll arbUpload $ \upl -> 
+    let 
+         parsed :: Either String (IsDocumentation, Package)  
+         parsed = (parseTgzFilename' $ fileToUpload upl )  
+
+    in case parsed of
+            Right (isDoc, Package parsedName parsedVer) -> 
+                  isDoc       == uploadType upl 
+              &&  parsedName  == packageName    ( package upl)
+              &&  parsedVer   == packageVersion ( package upl)
+            Left  msg -> 
+                  False
+
+
+
diff --git a/src-hspec-test/Distribution/Hup/ParseSpec.hs b/src-hspec-test/Distribution/Hup/ParseSpec.hs
new file mode 100644
--- /dev/null
+++ b/src-hspec-test/Distribution/Hup/ParseSpec.hs
@@ -0,0 +1,25 @@
+
+
+module Distribution.Hup.ParseSpec where
+
+import Test.Hspec
+import qualified Distribution.Hup.Parse.Test
+
+arbWord =  Distribution.Hup.Parse.Test.arbWord 
+
+prop_parseTgzFilename_roundtripsOK =
+  Distribution.Hup.Parse.Test.prop_parseTgzFilename_roundtripsOK
+
+
+-- `main` is here so that this module can be run from GHCi on its own.  It is
+-- not needed for automatic spec discovery.
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = 
+  describe "parseTgzFilename" $ 
+    it "should round-trip back to original name, version, package type"  
+      prop_parseTgzFilename_roundtripsOK
+
+
diff --git a/src-hspec-test/Distribution/Hup/Upload/Test.hs b/src-hspec-test/Distribution/Hup/Upload/Test.hs
new file mode 100644
--- /dev/null
+++ b/src-hspec-test/Distribution/Hup/Upload/Test.hs
@@ -0,0 +1,99 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE CPP #-}
+
+{- | Support for testing  Distribution.Hup.Upload 
+
+-}
+
+module Distribution.Hup.Upload.Test where
+
+import qualified Data.ByteString.Lazy.Char8 as LBS   --   pack
+import qualified Network.HTTP.Client as HTTP.Client
+import System.FilePath              ( (</>) )
+import System.IO.Temp               (withSystemTempDirectory)
+import Test.QuickCheck
+import Test.QuickCheck.Monadic      (run, assert, monadicIO)
+
+import Distribution.Hup.Upload  
+import Distribution.Hup.Parse.Test
+
+type ParsedTgz = Either String (IsDocumentation, Package) 
+
+arbAuth =
+  mkAuth <$> arbitrary <*> arbitrary
+
+
+-- | Round-trips an http request to check things seem to be going to the
+-- right URLs.
+--
+-- Doesn't check the file/body, just metadata.
+httpRoundTripsOK'
+  :: (HTTP.Client.Request -> IO Response)
+     -> Int -> Property
+httpRoundTripsOK' sendRequest port = 
+  forAll arbUpload $ \upl ->
+    forAll arbAuth $ \auth ->
+      httpRoundTripsOK  sendRequest port upl auth
+
+
+httpRoundTripsOK sendRequest port upl auth = 
+      monadicIO $ do
+        response <- run $ emptyFileRequest port upl auth
+        assert $ statusCode response == 200
+
+        let bod = LBS.unpack $ responseBody response
+            _unserialized :: (IsDocumentation, IsCandidate, ParsedTgz)
+            _unserialized@(recdIsDoc1, recdIsCand, parsedTgz) = read bod
+
+        let sentIsCand = isCandidate upl
+            sentIsDoc  = uploadType  upl
+            sentPkg    = package     upl 
+
+        assert (parsedTgz == Right (sentIsDoc, sentPkg) )
+        assert (sentIsCand == recdIsCand)
+        assert (sentIsDoc  == recdIsDoc1)
+  where
+  emptyFileRequest :: Int -> Upload -> Maybe Auth -> IO Response
+  emptyFileRequest port upl auth = 
+    withSystemTempDirectory "huptest" $ \tmpDir -> do
+      let newFile = tmpDir </> fileToUpload upl
+      upl <- return $ upl { fileToUpload = newFile } 
+      writeFile (tmpDir </> fileToUpload upl) ""
+      let url = "http://localhost:" ++ show port ++ "/"
+      buildRequest url upl auth >>= sendRequest
+
+
+-- | Round-trips an http request to check things seem to be going to the
+-- right URLs.
+--
+-- Doesn't check the file/body, just metadata.
+badUrlReturns'
+  :: (HTTP.Client.Request -> IO Response)
+     -> Int -> Property
+badUrlReturns' sendRequest port = 
+  forAll arbUpload $ \upl ->
+    forAll arbAuth $ \auth ->
+      badUrlReturns sendRequest port upl auth
+
+
+
+-- | Given a bad url, the http library should return a 
+-- non-2XX status code, rather than throwing an exception.
+badUrlReturns sendRequest port upl auth = 
+  monadicIO $ do
+    response <- run $ badRequest port upl auth
+    assert $ statusCode response /= 200
+
+  where
+  badRequest :: Int -> Upload -> Maybe Auth -> IO Response
+  badRequest port upl auth = 
+    withSystemTempDirectory "huptest" $ \tmpDir -> do
+      let newFile = tmpDir </> fileToUpload upl
+      upl <- return $ upl { fileToUpload = newFile } 
+      writeFile (tmpDir </> fileToUpload upl) ""
+      let url = "http://localhost:" ++ show port ++ "/fubar/"
+      buildRequest url upl auth >>= sendRequest
+
+
+
+
diff --git a/src-hspec-test/Distribution/Hup/UploadSpec.hs b/src-hspec-test/Distribution/Hup/UploadSpec.hs
new file mode 100644
--- /dev/null
+++ b/src-hspec-test/Distribution/Hup/UploadSpec.hs
@@ -0,0 +1,173 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Distribution.Hup.UploadSpec where
+
+
+
+
+import Control.Exception                      (throwIO)
+import Control.Monad
+import Control.Monad.IO.Class                 (liftIO)
+import qualified Data.ByteString.Char8 as BS
+import qualified Data.ByteString.Lazy.Char8 as LBS   --   pack
+import Data.Maybe                             (fromJust)
+import Data.Monoid                            ( (<>) )
+import Network.HTTP.Client.MultipartFormData  (renderParts,webkitBoundary)
+import Network.HTTP.Types as T                (statusCode,methodPost
+                                              ,StdMethod(..)) 
+import Network.Wai.Parse as Parse             (FileInfo(..), fileName)
+import Network.Wai.Test                       (simpleStatus,SResponse
+                                              ,simpleBody)
+import Test.Hspec
+import qualified Test.Hspec.Wai as HWai       --(put, request)
+import Test.Hspec.Wai.Internal                --(WaiSession,runWaiSession)
+import Test.QuickCheck                        --(forAll)
+import Test.QuickCheck.Monadic                --(assert, run, monadicIO )
+import Web.Simple --(Application,controllerApp, ControllerT,Controller,ok,parseForm, queryParam, rawPathInfo, request, respond, routeMethod, routePattern)
+
+
+import Distribution.Hup.Upload  
+import Distribution.Hup.Parse 
+import Distribution.Hup.Parse.Test
+
+import qualified Distribution.Hup.WebTest 
+
+type ParsedTgz = Either String (IsDocumentation, Package) 
+
+
+-- `main` is here so that this module can be run from GHCi on its own.  It is
+-- not needed for automatic spec discovery.
+main :: IO ()
+main = 
+  hspec spec 
+
+spec :: Spec
+spec = do
+  describe "testing with mocked requests" $ 
+    describe "mocked requests" $
+      context "when processed by a mock hackage server" $ 
+        it "should go to the right web app path" 
+          httpMetadataRoundtripsOK'  
+  describe "testing with live HTTP requests" $ 
+    -- this will be replaced with a stub unless the WEB_TESTS macro
+    -- is defined.
+    Distribution.Hup.WebTest.liveTest webApp
+
+
+
+httpMetadataRoundtripsOK' = 
+  forAll arbUpload $ \upl -> httpMetadataRoundtripsOK upl 
+
+
+httpMetadataRoundtripsOK upl = monadicIO $ do 
+  upl <- return $ upl { fileConts = Just "" }
+  testRequest <- run $ buildTestRequest "" upl 
+  testResponse <- run $ sendTestRequest testRequest
+
+  let resStatus = T.statusCode $ simpleStatus testResponse
+      resBody :: String
+      resBody =   LBS.unpack $ simpleBody testResponse
+      _unserialized :: (IsDocumentation, IsCandidate, ParsedTgz)
+      _unserialized@(recdIsDoc1, recdIsCand, parsedTgz) = read resBody
+
+  let sentIsCand = isCandidate upl
+      sentIsDoc  = uploadType  upl
+      sentPkg    = package     upl
+
+  assert (resStatus == 200)
+  assert (sentIsCand == recdIsCand)
+  assert (sentIsDoc  == recdIsDoc1)
+  assert (parsedTgz == Right (sentIsDoc, sentPkg) )
+
+
+sendTestRequest :: WaiSession SResponse -> IO SResponse
+sendTestRequest testReq =   
+  runWaiSession testReq webApp
+
+--showInd x =
+--  indent 4 (show x) 
+--
+--indent n = 
+--  let wspace = replicate n ' ' 
+--  in  unlines .  (map (wspace ++)) . lines
+
+testPut :: String -> LBS.ByteString -> WaiSession SResponse
+testPut url conts = 
+  HWai.put (BS.pack url) conts
+
+testPost
+  :: String -> FilePath -> LBS.ByteString -> IO (WaiSession SResponse)
+testPost url fileName fileConts = do
+  boundary <- webkitBoundary 
+  let part    = mkPart fileName fileConts
+      headers = [("Content-Type", 
+                    "multipart/form-data; boundary=" <> boundary)]
+  body <- bodyToByteString <$> renderParts boundary [part]
+  return $ HWai.request T.methodPost (BS.pack url) headers body
+
+
+-- Only call when fileConts has something in it.
+buildTestRequest
+  :: String -> Upload -> IO (WaiSession SResponse)
+buildTestRequest serverUrl upl  = do
+  let (Upload _ filePath fileConts uploadType _pkgType ) = upl
+      url = getUploadUrl serverUrl upl
+  fileConts <- return (fromJust fileConts)
+  case uploadType of
+      IsPackage -> 
+         testPost url filePath fileConts
+      IsDocumentation -> 
+         return $ testPut url fileConts 
+
+
+ioAssert pred mesg = 
+  unless pred $
+        liftIO $ throwIO $ userError mesg
+
+-- | We send the parsed results back as plain text,
+-- parseable by 'Text.Read.read'.
+webApp :: Application
+webApp = controllerApp () $ do
+    myReq <- request 
+    routeMethod T.POST $ do path <- rawPathInfo <$> request
+                            let isCand = if "/candidates/" `BS.isSuffixOf`path
+                                         then CandidatePkg
+                                         else NormalPkg  
+                            when (path == "/packages/" || 
+                                 path == "/packages/candidates/") $ do
+                                    (_params, files) <- parseForm
+                                    handlePost isCand files
+    routeMethod T.PUT $ routePattern "/package/:pkgVer/:isCand" $ do
+                            pkgVer <- fromJust <$> queryParam "pkgVer"  
+                            let filename = pkgVer :: String
+                            isCand <- fromJust <$> queryParam "isCand" 
+                            let isCand' = if ("candidate" :: String) == isCand
+                                         then CandidatePkg
+                                         else NormalPkg
+                            let remainingBit = if "candidate"== isCand
+                                          then "docs"
+                                          else ""
+                            routePattern remainingBit $ 
+                              handlePut isCand' filename
+  where 
+    handlePost :: IsCandidate -> [(a, FileInfo c)] -> ControllerT s IO b
+    handlePost isCand files = do
+      ioAssert (length files == 1)
+               "posted package should have exactly 1 file" 
+      let filename = BS.unpack $ Parse.fileName $ snd $ head files
+          parsed :: Either String (IsDocumentation, Package)  
+          parsed = parseTgzFilename' filename
+      respond $ ok "text/plain" $ LBS.pack $ show (IsPackage, isCand, parsed)
+
+    handlePut :: IsCandidate -> FilePath -> Controller s a
+    handlePut isCand filename = do
+      let parsed :: Either String (IsDocumentation, Package)  
+          parsed = parseTgzFilename' (filename ++ "-docs.tar.gz")
+      respond $ ok "text/plain" $ LBS.pack $ 
+                show (IsDocumentation, isCand, parsed)
+
+
+
+
+
+
diff --git a/src-hspec-test/Distribution/Hup/WebTest.hs b/src-hspec-test/Distribution/Hup/WebTest.hs
new file mode 100644
--- /dev/null
+++ b/src-hspec-test/Distribution/Hup/WebTest.hs
@@ -0,0 +1,81 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE CPP #-}
+
+module Distribution.Hup.WebTest where
+
+
+import Distribution.Hup.Upload                (sendRequest)  
+import Distribution.Hup.Upload.Test
+import Distribution.Hup.Parse 
+import Network.Wai                            (Application)
+import Test.Hspec
+import Test.Hspec.Core.QuickCheck             (modifyMaxSuccess)
+import Test.QuickCheck
+
+#ifdef WEB_TESTS
+
+import Control.Concurrent                     (forkIO, ThreadId)
+import qualified Network.Socket as Soc        (Socket, close)
+import Network.Wai.Handler.Warp               (Port, defaultSettings
+                                              ,runSettingsSocket)
+
+
+#if MIN_VERSION_warp(3,2,4)
+import qualified Network.Wai.Handler.Warp as Warp  (openFreePort)
+
+openFreePort = Warp.openFreePort
+#else
+import Network.Socket
+
+openFreePort :: IO (Port, Soc.Socket)
+openFreePort = do 
+  s <- socket AF_INET Stream defaultProtocol
+  localhost <- inet_addr "127.0.0.1"
+  bind s (SockAddrInet aNY_PORT localhost)
+  listen s 1
+  port <- socketPort s
+  return (fromIntegral port, s)
+#endif
+
+-- Pulls a "Right" value out of an Either value.  If the Either value is
+-- Left, raises an exception with "error".
+forceEither :: Show e => Either e a -> a
+forceEither (Left x) = error (show x)
+forceEither (Right x) = x
+
+startServer :: Application -> IO (Port, Soc.Socket, ThreadId)
+startServer webApp = do
+  (port, sock) <- openFreePort 
+  tid <- forkIO $ runSettingsSocket defaultSettings sock webApp
+  return (port, sock, tid) 
+
+shutdownServer :: (Port, Soc.Socket, ThreadId) -> IO ()
+shutdownServer (_port, sock, _tid) = 
+  Soc.close sock
+
+liveTest :: Application -> SpecWith ()
+liveTest webApp = do
+    beforeAll (startServer webApp) $ afterAll shutdownServer $ 
+      describe "buildRequest" $ do
+        context "when its result is fed into sendRequest" $ 
+          modifyMaxSuccess (const 50) $
+            it "should send to the right web app path" $ \(port, sock, tid) -> 
+              httpRoundTripsOK' sendRequest' port
+
+        context "when given a bad URL" $ 
+          modifyMaxSuccess (const 50) $
+            it "should not throw an exception" $ \(port, sock, tid) -> 
+              badUrlReturns' sendRequest' port
+
+sendRequest' req = forceEither <$> sendRequest req
+
+
+#else
+
+liveTest :: Application -> SpecWith ()
+liveTest _ = return ()
+
+#endif
+
+
+
diff --git a/src-hspec-test/Spec.hs b/src-hspec-test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/src-hspec-test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/src/CmdArgs.hs b/src/CmdArgs.hs
--- a/src/CmdArgs.hs
+++ b/src/CmdArgs.hs
@@ -2,6 +2,7 @@
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# OPTIONS_GHC -fno-cse #-}
 {-# OPTIONS_GHC -fno-warn-missing-fields #-}
+{-# LANGUAGE ViewPatterns #-}
 
 module CmdArgs where
 
@@ -13,6 +14,34 @@
 import qualified DefaultServerUrl
 import CmdArgs.PatchHelp (cmdArgs)
 
+isDoc :: HupCommands -> Bool
+isDoc cmd = case cmd of
+  Docbuild {} -> True
+  Docboth {} -> True
+  _           -> False
+
+isBuild :: HupCommands -> Bool
+isBuild cmd = case cmd of
+  Docbuild {}   -> True
+  Packbuild {}  -> True
+  _             -> False
+
+isBoth :: HupCommands -> Bool
+isBoth cmd = case cmd of
+  Docboth {}  -> True
+  Packboth {} -> True
+  _           -> False
+
+isUp :: HupCommands -> Bool
+isUp cmd = case cmd of
+  Docup {} -> True
+  Packup {} -> True
+  _         -> False
+
+
+
+
+defaultServer :: String
 defaultServer = DefaultServerUrl.defaultServerUrl 
 
 -- | Actions the program can perform
@@ -33,6 +62,8 @@
               , candidate :: Bool
               , user     :: Maybe String 
               , password :: Maybe String  }
+
+  | Packbuild { verbose :: Bool }
               
   | Packup    { verbose  :: Bool 
               , server   :: String
@@ -40,6 +71,14 @@
               , user     :: Maybe String 
               , password :: Maybe String
               , file     :: String }
+
+  | Packboth  { verbose  :: Bool 
+              , server   :: String
+              , candidate :: Bool
+              , user     :: Maybe String 
+              , password :: Maybe String
+              }
+
   | Docup     { verbose  :: Bool 
               , server   :: String 
               , candidate :: Bool
@@ -56,22 +95,36 @@
 -- arguments.
 
 {-# INLINE serverArg #-}
+serverArg :: Data val => val -> val
 serverArg x = x &= typ  "URL"
 
 {-# INLINE userArg #-}
+userArg :: Data val => val -> val
 userArg   x = x &= typ  "USER"
 
 {-# INLINE passwdArg #-}
+passwdArg :: Data val => val -> val
 passwdArg x = x &= typ  "PASSWORD"
 
 {-# INLINE fileArg #-}
+fileArg :: Data val => val -> val
 fileArg x = x &= typ "FILE" 
 
 {-# INLINE verbArgs #-}
+verbArgs :: Data val => val -> val
 verbArgs x = x &= help "be verbose" 
 
 -- commands that can be run
 
+packbuild :: HupCommands
+packbuild =
+  Packbuild
+    { verbose     = verbArgs  def
+    }
+      &= help     "Build source distribution .tgz for a package."
+
+
+packup :: HupCommands
 packup = 
   Packup
     { verbose    = verbArgs   def
@@ -81,8 +134,20 @@
       ,user      = userArg    Nothing  
       ,password  = passwdArg  Nothing }
        &= help     (unwords   ["Upload FILE as a package (or"
-                                              ,"candidate package)."])
+                               ,"candidate package)."])
 
+packboth :: HupCommands
+packboth =
+  Packboth
+    { verbose    = verbArgs   def
+      ,server    = serverArg  defaultServer  
+      ,candidate =            def  
+      ,user      = userArg    Nothing  
+      ,password  = passwdArg  Nothing }
+       &= help     (unwords   ["Build source distribution .tgz and upload"
+                               ,"as package (or candidate package)."])
+
+docbuild :: HupCommands
 docbuild = 
   Docbuild
     { verbose     = verbArgs  def
@@ -100,6 +165,7 @@
      }
       &= help     "Build documentation for a package."
 
+docup :: HupCommands
 docup =
   Docup 
     { server = serverArg  defaultServer 
@@ -107,21 +173,25 @@
      }
      &= help "Upload FILE as documentation."
 
+docboth :: HupCommands
 docboth = 
   Docboth 
     {}
     &= help "Build and upload documentation for a package."
 
 -- Process command-line arguments
+processArgs :: IO HupCommands
 processArgs = do
    args <- getArgs
-    -- If the user did not specify any arguments, pretend as "--help" was given
+    -- If the user did not specify any arguments, pretend "--help" was given
    (if null args then withArgs ["--help"] else id) proc
 
   where 
   proc :: IO HupCommands
   proc = cmdArgs $ -- commands that can be run, i.e. "modes"
-           modes [packup  
+           modes [packbuild
+                 ,packup  
+                 ,packboth
                  ,docbuild -- &= groupname "A" 
                  ,docup    -- &= groupname "B"
                  ,docboth ]  -- &= groupname "C"] 
diff --git a/src/DefaultServerUrl.hs b/src/DefaultServerUrl.hs
--- a/src/DefaultServerUrl.hs
+++ b/src/DefaultServerUrl.hs
@@ -2,7 +2,7 @@
 
 module DefaultServerUrl where
 
-
+-- | The default hackage server URL.
 defaultServerUrl :: String
 defaultServerUrl = "https://hackage.haskell.org/"
 --defaultServerUrl = "http://localhost:8080/"
diff --git a/src/DocBuilding.hs b/src/DocBuilding.hs
new file mode 100644
--- /dev/null
+++ b/src/DocBuilding.hs
@@ -0,0 +1,207 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ExtendedDefaultRules #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+
+{- |
+
+Knowledge of the arcane and cryptic commands to be run to get
+haddock docs properly built.
+
+-}
+
+module DocBuilding 
+(
+-- * given a 'HupCommand', what arguments are needed
+    verbosityArgs
+  , haddockExtraArgs 
+  , cabalExtraArgs 
+-- * actions
+  , buildDependencyDocs
+  , cabalConfigure
+  , cabalHaddock
+  , doBuildTar
+-- * top-level action
+  , buildDocs
+-- * path utilities
+  , buildDir
+)
+where
+
+import CmdArgs                    (HupCommands(..), isUpload, processArgs)
+import Data.Text                  ( Text )
+import qualified Data.Text as T
+import Data.Monoid                ( (<>) )
+import Shelly.Lifted
+import Data.Char                  (isSpace)
+import Prelude        hiding      (FilePath)
+import Control.Monad.IO.Class     ( MonadIO(..) )
+import qualified Stack 
+import Distribution.Hup           ( Package(..), buildTar )
+import Control.Exception
+import Shelly                     ( ReThrownException ) 
+
+-- | just a convenience alias, short for @'MonadIO' m, 'MonadSh' m,
+--  'MonadShControl' m@
+type MonadShellish m = (MonadIO m, MonadSh m, MonadShControl m) 
+
+rstrip :: Text -> Text
+rstrip = T.dropWhileEnd isSpace
+
+
+-- | any arguments for cabal deriving from how verbose
+-- we've been asked to be
+verbosityArgs :: HupCommands -> [Text]
+verbosityArgs hc = if verbose hc then ["-v2"] else [] 
+
+-- | arguments to supply directly to haddock, such as "--executables",
+-- "--internal", or anything specified by the end user
+haddockExtraArgs :: HupCommands -> [Text]
+haddockExtraArgs hc = 
+  let args = haddockArgs hc 
+  in (if null args
+     then []
+     else ["--haddock-options=" <>T.pack(haddockArgs hc)])
+   ++ (if executables hc then ["--executables"] else [])
+   ++(if tests hc then ["--tests"] else [])
+   ++(if internal hc then ["--internal"] else [])
+
+-- | any arguments which need to be supplied to cabal,
+-- like "--enable-tests"
+cabalExtraArgs :: HupCommands -> [Text]
+cabalExtraArgs hc = if tests hc then ["--enable-tests"] else []
+
+
+-- | run haddock with "--only-dependencies"
+buildDependencyDocs :: MonadSh m => m ()
+buildDependencyDocs = do
+  run_ "stack" ["haddock", "--only-dependencies"]
+
+-- | given a "base" dir, return the "dist" subdir
+-- where built stuff actually gets put.
+buildDir :: FilePath -> FilePath
+buildDir baseDir = baseDir </> "dist"
+
+-- | run "cabal configure" with appropriate args, prior to
+--  running "cabal haddock". cabal should be on path.
+--
+-- args: @cabalConfigure baseDir cabalExtraArgs verbosityArgs@.
+cabalConfigure
+  :: MonadShellish m =>
+     FilePath -> [Text] -> [Text] -> m ()
+cabalConfigure baseDir cabalExtraArgs verbosityArgs =  do
+  let tt = toTextIgnore 
+  snapshotpkgdb <- rstrip <$> silently (run "stack" ["path", "--snapshot-pkg-db"])
+  localpkgdb    <- rstrip <$> silently (run "stack" ["path", "--local-pkg-db"])
+  run_ "cabal" $ ["configure", "--builddir="<> tt (buildDir baseDir), 
+                  "--package-db=clear", "--package-db=global", 
+                  "--package-db=" <> snapshotpkgdb, 
+                  "--package-db=" <> localpkgdb] ++ verbosityArgs 
+                  ++ cabalExtraArgs
+
+-- | run "cabal haddock" with appropriate args. Haddock
+-- should be on path.
+--
+-- args: @cabalHaddock baseDir verbosityArgs haddockExtraArgs@
+--   
+cabalHaddock
+  :: MonadShellish m => FilePath -> [Text] -> [Text] -> m ()
+cabalHaddock baseDir verbosityArgs haddockExtraArgs = do
+  let tt = toTextIgnore 
+  canHyperlink <- Stack.haddockCanHyperlinkSrc 
+  let hyperlinkArgs = if canHyperlink
+                      then ["--haddock-option=--hyperlinked-source"]
+                      else []
+  run_ "cabal" $ ["haddock", "--builddir=" <> tt (buildDir baseDir),  
+                  "--html-location=/package/$pkg-$version/docs",
+                  "--contents-location=/package/$pkg-$version"] 
+                  ++ hyperlinkArgs ++ verbosityArgs
+                  ++ haddockExtraArgs 
+
+
+-- | Tar up the html produced by haddock into a "-docs.tar.gz" file.
+-- Returns the path of the tar file that has been built.
+--
+-- args: @doBuildTar baseDir package@.
+doBuildTar :: MonadShellish m => FilePath -> Package -> m FilePath
+doBuildTar baseDir (Package pkg_ ver_) = do
+  let
+    pkg = T.pack pkg_
+    ver = T.pack ver_
+    fromPath = T.unpack . toTextIgnore 
+    docTgz = fromPath $ baseDir </> (pkg<>"-"<>ver <> "-docs.tar.gz") 
+    docDir = pkg <> "-" <> ver <> "-docs" 
+  -- build tar using pure hs. or, if you have tar on system, could use:
+  --    run "tar" ["cvz", "-C", dir, "--format=ustar", "-f", docTgz,
+  --                pkg <> "-" <> ver <> "-docs" ]
+  liftIO $ buildTar docTgz (fromPath baseDir) [T.unpack docDir]
+  return $ fromText $ T.pack docTgz
+
+-- Shelly exceptions end up being (almost) endlessly wrappered IOErrors ...
+-- but here's a convenience function for catching what they look like at 
+-- the top level.
+catch_sh_rethrown :: Sh a -> (ReThrownException SomeException -> Sh a) -> Sh a
+catch_sh_rethrown = catch_sh
+
+-- | Build a documentation .tgz file.
+--
+-- A Haskellified version of
+-- phadej's script at
+--
+--    <https://github.com/phadej/binary-orphans/blob/master/hackage-docs.sh>,
+--
+-- which is a stack-enabled version of ekmett's script at
+--
+--    <https://github.com/ekmett/lens/blob/master/scripts/hackage-docs.sh>.
+--
+-- @stackBuildDocs baseDir pkg@ will build documentation in the
+-- directory @baseDir@.
+--
+-- Requires that stack, haddock and cabal be on the path.
+--
+-- Sample usage:
+--
+-- > :set -XOverloadedStrings 
+-- > let p = Package "foo" "0.1"
+-- > shelly $ verbosely $ buildDocs "." p 
+--
+-- When running from within ghci, you may have to unset some
+-- environment variables that have been set. Else cabal will complain
+-- about them.
+--
+-- > import System.Environment
+-- > import Control.Monad
+-- > mapM_ unsetEnv ["HASKELL_PACKAGE_SANDBOXES", "GHC_PACKAGE_PATH", "HASKELL_PACKAGE_SANDBOX", "STACK_EXE", "HASKELL_DIST_DIR"]
+--
+buildDocs
+  :: MonadShellish m =>
+     HupCommands -> FilePath -> Package -> m FilePath
+buildDocs hc tmpDir pkg = 
+  do
+    unless (quick hc) $ do
+      echo "building dependency docs"
+      buildDependencyDocs
+    echo "configuring"
+    cabalConfigure tmpDir (verbosityArgs hc) (cabalExtraArgs hc)
+    echo "running haddock"
+    cabalHaddock tmpDir (verbosityArgs hc) (haddockExtraArgs hc)
+    echo "copying html files"
+    copyHtmlDir (toTextIgnore tmpDir) pkg
+    echo "building tar file"
+    doBuildTar tmpDir pkg
+  where
+    -- copy the html files from where haddock puts them to 
+    -- somewhere we can tar them from.
+    copyHtmlDir :: MonadShellish m => Text -> Package -> m ()
+    copyHtmlDir baseDir (Package pkg_ ver_) = do
+      let pkg = T.pack pkg_
+          ver = T.pack ver_
+          srcDir = baseDir </> "dist" </> "doc" </> "html" </> pkg
+          tgtDir = baseDir </> (pkg <> "-" <> ver <> "-docs")
+      echo $ "copying from " <> (toTextIgnore srcDir) <> " to " <> (toTextIgnore tgtDir)
+      liftSh $ catch_sh_rethrown (errExit False $ cp_r srcDir tgtDir) $ \e -> do
+        echo $ T.unwords ["hup: Encountered exception trying to copy html"
+                          , "documentation:", T.pack $ show e ]
+        quietExit 1
+
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -5,6 +5,8 @@
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE CPP #-}
 
 module Main where
@@ -12,93 +14,56 @@
 import Prelude hiding (FilePath)
 import qualified Prelude
 
+import Control.Monad
 import Control.Monad.IO.Class     ( MonadIO(..) )
-import Control.Monad.Reader       (MonadReader(..), runReaderT, liftM)
+import Control.Monad.Reader       (MonadReader(..), runReaderT)
 import Control.Monad.Trans        (lift)
 import Control.Monad.Trans.Maybe  (MaybeT(..))
 import Control.Monad.Trans.Except (ExceptT(..),runExceptT,throwE)
-import Data.Char                  (isSpace, isDigit)
 import Data.Either                (either)
-import Data.List                  (isPrefixOf)
-import Data.Maybe                 (isNothing)
 import Data.Text                  ( Text )
 import qualified Data.Text as T
 import Data.Monoid                ( (<>) )
 import Shelly.Lifted
-import System.Directory           -- (listDirectory)
 import System.IO                  (hSetBuffering, BufferMode( LineBuffering )
                                   , stdout)
+--import qualified System.IO.Temp as Tmp
 
 import Distribution.Hup           ( Package(..),IsDocumentation(..)
                                   ,IsCandidate(..),Auth(..),Upload(..)
-                                  ,mkAuth ,buildTar
+                                  ,mkAuth 
                                   ,readCabal, extractCabal
                                   ,parseTgzFilename' 
                                   , getUploadUrl)
-import CmdArgs                    (HupCommands(..), isUpload, processArgs)
+import CmdArgs                    (HupCommands(..), isUpload, processArgs
+                                  ,isBuild, isBoth, isDoc)
+import DocBuilding                (buildDocs) 
 import SanityCheck                (sanity)
+import qualified Stack 
 import Upload                     (doUpload)
 
-#if MIN_VERSION_directory(1,2,5)
-#else
-listDirectory :: Prelude.FilePath -> IO [Prelude.FilePath]
-listDirectory path =
-  filter f <$> getDirectoryContents path
-  where f filename = filename /= "." && filename /= ".."
-#endif
 
--- | just a convenience alias, short for @'MonadIO' m, 'MonadSh' m,
---  'MonadShControl' m
-type MonadShellish m = (MonadIO m, MonadSh m, MonadShControl m) 
 
+
+
 -- | just a convenience alias, short for @'MonadIO' m, 'MonadSh' m,
 -- 'MonadShControl' m, 'MonadReader' 'HupCommands' m@
 type MonadHup m = (MonadIO m, MonadSh m, MonadShControl m, MonadReader HupCommands m)
 
-rstrip :: Text -> Text
-rstrip = T.dropWhileEnd isSpace 
-
--- | does haddock have ability to do hyperlinked source.
-haddockCanHyperlinkSrc :: MonadShellish m  => m Bool
-haddockCanHyperlinkSrc = errExit False $ do
-  run_ "haddock" ["--hyperlinked-source"]
-  (==0) `liftM` lastExitCode
-
--- | safe version of "maximum"
-maxMay xs = if null xs then Nothing else Just $ maximum xs
-
--- | look for ghc & try to add it to path
-addGhcPath :: MonadShellish m => m ()
-addGhcPath = 
-  -- see if ghc is already in path;
-  -- if not, try looking for it in stack's "programs" directory.
-  whenM (isNothing `liftM` which "ghc") $ do
-    progPath <- rstrip <$> silently (run "stack" ["path", "--programs"])
-    let myFilt x = "ghc" `isPrefixOf` x && isDigit (last x)
-    ghcDir <- liftIO $ (maxMay . filter myFilt) `liftM` listDirectory (T.unpack progPath)
-    case ghcDir of
-      Nothing -> terror "couldn't find ghc on path, please add it"
-      Just ghcDir' -> appendToPath $ progPath </> ghcDir' </> "bin"
-
-
--- | Run commands for building documentation tgz. A Haskellified version of
--- phadej's script at
---    https://github.com/phadej/binary-orphans/blob/master/hackage-docs.sh,
--- which is a stack-enabled version of ekmett's script at
---    https://github.com/ekmett/lens/blob/master/scripts/hackage-docs.sh.
---
--- `stackBuildDocs dir pkg CandidatePkg` will build documentation in the
--- directory `dir`, and will return an `Upload` object, which can be uploaded
--- with `Distribution.Hup.Upload.doUpload`.
+-- | Build documentation tgz, return an Upload value,
+-- return an Upload value, ready to be uploaded. (Or not, as desired.)
 --
--- Requires that stack, haddock and cabal be on the path.
+-- This installs cabal-install if it is not found in the snapshot
+-- binaries, and adds the path to the cabal executable, and to
+-- haddock, to the path.
 --
 -- Sample usage:
 --
 -- > import Distribution.Hup.Upload 
 -- > :set -XOverloadedStrings 
 -- > let p = Package "foo" "0.1"
--- > upload <- shelly $ stackBuildDocs "." p CandidatePkg 
+-- > let d = Docbuild { verbose = True }
+-- > upload <- shelly $ verbosely $ runReaderT (stackBuildDocs "." p) d
 -- > doUpload "http://localhost:8080" upload (mkAuth "myname" "mypass") 
 --
 -- When running from within ghci, you may have to unset some
@@ -107,58 +72,41 @@
 -- > import System.Environment
 -- > import Control.Monad
 -- > mapM_ unsetEnv ["HASKELL_PACKAGE_SANDBOXES", "GHC_PACKAGE_PATH", "HASKELL_PACKAGE_SANDBOX", "STACK_EXE", "HASKELL_DIST_DIR"]
---
 stackBuildDocs :: MonadHup m => FilePath -> Package -> m Upload
-stackBuildDocs dir (Package pkg ver) = do
+stackBuildDocs tmpDir pkg = do
   hc <- ask
+  -- check paths for the tools we need
+  Stack.addHaddockPath
+  echo "checking for cabal"
+  haveCabal <- Stack.cabalInstalled
+  unless haveCabal $ do
+    echo "didn't find 'cabal' binary in snapshot directory - installing it"
+    Stack.installCabal
+  Stack.addCabalPath
+  -- build the tgz file
+  docTgz <- toTextIgnore <$> buildDocs hc tmpDir pkg
+  -- return an Upload value, ready to be uploaded. (Or not, as desired.)
+  return $ Upload pkg (T.unpack docTgz) Nothing IsDocumentation (isCand hc)
+
+
+-- | run "stack sdist" and copy tarball to current dir.
+--
+-- sample use - something like:
+--
+-- > let p = Package "foo" "0.1"
+-- > let d = Pacbuild { verbose = True }
+-- > shelly $ verbosely $ runReaderT (stackSourceDist p) d
+--
+stackSourceDist :: MonadHup m => Package -> m Upload
+stackSourceDist p@(Package pkg ver) = do
   -- work out flags to call with ...
-  canHyperlink <- haddockCanHyperlinkSrc 
-  let hyperlinkFlag = if canHyperlink
-                      then ["--haddock-option=--hyperlinked-source"]
-                      else []
-  unless (quick hc) $ do
-    echo "build dependencies docs"
-    run_ "stack" ["haddock", "--only-dependencies"]
-  snapshotpkgdb <- rstrip <$> silently (run "stack" ["path", "--snapshot-pkg-db"])
-  localpkgdb    <- rstrip <$> silently (run "stack" ["path", "--local-pkg-db"])
-  let verboseCommands  = if verbose hc then ["-v2"] else [] 
-  let haddockExtraArgs = let args = haddockArgs hc 
-                         in (if null args
-                            then []
-                            else ["--haddock-options=" <>T.pack(haddockArgs hc)])
-                          ++ (if executables hc then ["--executables"] else [])
-                          ++(if tests hc then ["--tests"] else [])
-                          ++(if internal hc then ["--internal"] else [])
-  let cabalExtraArgs =(if tests hc then ["--enable-tests"] else [])
-  echo "configuring"
-  let builddir= toTextIgnore $ dir </> "dist"
-  run_ "cabal" $["configure", "--builddir="<>builddir, 
-               "--package-db=clear", "--package-db=global", 
-               "--package-db=" <> snapshotpkgdb, 
-               "--package-db=" <> localpkgdb] ++ verboseCommands
-               ++ cabalExtraArgs
-  echo "making docs"
-  run_ "cabal" $["haddock", "--builddir=" <> builddir,  
-               "--html-location=/package/$pkg-$version/docs",
-               "--contents-location=/package/$pkg-$version"] 
-               ++ hyperlinkFlag ++ verboseCommands
-               ++ haddockExtraArgs 
-  pkg <- return $ T.pack pkg
-  ver <- return $ T.pack ver
-  let srcDir = builddir </> "doc" </> "html" </> pkg
-      tgtDir = dir </> (pkg <> "-" <> ver <> "-docs")
-  cp_r srcDir tgtDir
-  echo "tarring"
-  let
-    up = T.unpack
-    fromPath = T.unpack . toTextIgnore 
-    docTgz = fromPath $ dir </> (pkg<>"-"<>ver <> "-docs.tar.gz") 
-    docDir = pkg <> "-" <> ver <> "-docs" 
-  -- or, if you have tar on system, could use:
-  --    run "tar" ["cvz", "-C", dir, "--format=ustar", "-f", docTgz,
-  --                pkg <> "-" <> ver <> "-docs" ]
-  liftIO $ buildTar docTgz (fromPath dir) [up docDir]
-  return $ Upload (Package (up pkg) (up ver)) docTgz Nothing IsDocumentation (isCand hc)
+  hc <- ask
+  run_ "stack" ["sdist"] -- if there are errors, just let them
+                         -- be re-thrown
+  distDir <- Stack.extractPath =<< run "stack" ["path", "--dist-dir"]
+  let tgzFile = pkg <> "-" <> ver <> ".tar.gz"
+  cp (distDir </> tgzFile) "."
+  return $ Upload p tgzFile Nothing IsPackage (isCand hc) 
 
 
 -- | if we have a username, then we need to get
@@ -174,7 +122,6 @@
                     Nothing -> terror "username specified, but no password"
                     Just p  -> MaybeT $ return $ mkAuth u (T.unpack p)
 
- 
 
 -- Use for "early return"
 data Done = Done
@@ -190,30 +137,12 @@
 done :: Monad m => ExceptT Done m a
 done = throwE Done
 
+isCand :: HupCommands -> IsCandidate
 isCand hc = 
   if candidate hc
   then CandidatePkg
   else NormalPkg 
 
-
-fromString = fromText . T.pack
-
--- | check that stack, cabal & haddock are on path
-checkPrereqs :: MonadSh m => m ()
-checkPrereqs = do
-  whenM (isNothing <$> which "stack") $  do
-    echo "Couldn't find stack on path - do you need to install stack?"
-    quietExit 1
-  whenM (isNothing <$> which "cabal") $ do
-    echo $ T.unwords ["Couldn't find cabal on path - do you need to"
-                      ,"run 'stack install cabal-install'? Exiting"]
-    quietExit 1
-  whenM (isNothing <$> which "haddock") $ do
-    echo $ T.unwords ["Couldn't find haddock on path - do you need to"
-                      ,"run 'stack install haddock'? Exiting"]
-    quietExit 1
-
-
 -- | Look at a FILE command-line arg of something we've been asked to
 -- upload, & try uploading it.
 --
@@ -262,13 +191,20 @@
 --
 -- sample usage:
 --
--- > let d = Docbuild { verbose == True }
+-- > let d = Docbuild { verbose = True }
 -- > shelly $ verbosely $ runReaderT $ mainSh d
 mainSh :: MonadHup m => m ()
 mainSh =  do  
   hc <- ask
-  withTmpDir $ \tmpDir -> 
-    runEarlyReturn $ do
+  --tmpBase <- liftIO $ Tmp.getCanonicalTemporaryDirectory
+  --tmpDir  <- fromText . T.pack <$> liftIO (Tmp.createTempDirectory tmpBase "hup")
+  --do
+
+  -- *  Bug intermittently occurred where this temp directory disappeared
+  --    partway thru mainSh running. Might've been caused by something
+  --    not being strict enough? This seems to fix it, so far *shrug*
+  withTmpDir $ \tmpDir -> do 
+    !res <- runEarlyReturn $ do
       cabalConts <- liftIO readCabal
       let packageName = extractCabal "name" cabalConts
           packageVer  = extractCabal "version" cabalConts 
@@ -277,14 +213,22 @@
                           throwE Done
         Docup  {}   -> do uploadTgz IsDocumentation "documentation"
                           throwE Done
-        _           -> return () -- i.e. carry on.
+        (isBuild -> True) -> return () -- i.e. carry on.
+        (isBoth  -> True) -> return ()
+        _                 -> error "match error"
       -- if still here, we've been asked to do a build.
       uploadable <- do let p = Package packageName packageVer
-                       buildRes <- lift $ stackBuildDocs tmpDir p  
-                       let tgzFile = fromText $ T.pack $ fileToUpload buildRes 
+                       buildRes <- case hc of 
+                          (isDoc -> True) ->  lift $ stackBuildDocs tmpDir p
+                          _               ->  lift $ stackSourceDist p
+                          
+                       let tgzFile = fromText $ T.pack $ fileToUpload buildRes
+
                        case hc of 
+                         Packbuild {} -> throwE Done -- build only
                          Docbuild {} -> lift (cp tgzFile ".") >>
-                                        throwE Done
+                                        throwE Done -- no need to upload,
+                                                    -- only build requested
                          _           -> return buildRes
       auth     <- lift $ getAuth hc
       let url = getUploadUrl (server hc) uploadable
@@ -295,6 +239,7 @@
                        throwE Done 
         Right msg -> lift $ do echo "Uploaded successfully"
                                echo $ "mesg was: " <> T.pack msg
+    return ()
 
 
 
@@ -306,9 +251,7 @@
                   then verbosely
                   else id
   shelly $ do
-    silently checkPrereqs
     verbosify $ do 
-      addGhcPath
       runReaderT mainSh hupCommand 
   return ()
 
diff --git a/src/SanityCheck.hs b/src/SanityCheck.hs
--- a/src/SanityCheck.hs
+++ b/src/SanityCheck.hs
@@ -1,5 +1,7 @@
 
-
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module SanityCheck where
 
@@ -8,11 +10,11 @@
 import Control.Monad.Trans.Except         (ExceptT(..),runExceptT, throwE)
 
 import Data.Monoid                        ( (<>) )
-import Shelly                             (whenM, unlessM)
+import Shelly                             (unlessM)
 import System.Directory                   (makeAbsolute,doesFileExist ) 
 import System.Exit
 
-import CmdArgs                            (HupCommands(..))
+import CmdArgs                            (HupCommands(..), isBoth, isBuild, isUp)
 import CmdArgs.PatchHelp                  (cmdArgs)
 
 -- TODO:
@@ -22,12 +24,16 @@
 --    - will ignore the sensibleness of haddockArgs, users can pass
 --      what they like
 --    - does the file look like a file?
+
+-- | Run some sanity checks over a HupCommand, e.g. confirm
+-- any files it refers to actually exist.
 sanity :: HupCommands -> IO HupCommands
-sanity hc =  do
-  let sanityTests = [fileSanity] -- TODO: add more sanity tests here <<
+sanity !hc =  do
+  putStrLn "running sanity checks"
+  let !sanityTests = [fileSanity] -- TODO: add more sanity tests here <<
       -- compose them
-      composedSanityTests = foldl (>=>) return sanityTests
-  res <- runExceptT $ composedSanityTests hc
+      !composedSanityTests = foldl (>=>) return sanityTests
+  !res <- runExceptT $ composedSanityTests hc
   case res of
     Left err -> do print err
                    exitFailure
@@ -37,15 +43,16 @@
 fileSanity
   :: MonadIO m => HupCommands -> ExceptT String m HupCommands
 fileSanity hc = case hc of
---  -- ignore things without a file arg
-  Docbuild {} -> return hc
-  Docboth  {} -> return hc
-  _           -> do let f = file hc
-                    absF <- liftIO $ makeAbsolute f
-                    unlessM ( liftIO $ doesFileExist absF ) $
-                      throwE $ "Cannot find a file '" <> f <> "'"
-                    let hc' = hc { file = absF }
-                    return hc'
+  -- ignore things without a file arg
+  (isBuild -> True) -> return hc
+  (isBoth -> True)  -> return hc
+  (isUp -> True) -> do let f = file hc
+                       absF <- liftIO $ makeAbsolute f
+                       unlessM ( liftIO $ doesFileExist absF ) $
+                         throwE $ "Cannot find a file '" <> f <> "'"
+                       let hc' = hc { file = absF }
+                       return hc'
+  _               -> error "match error, shouldn't be possible"
 
 
 
diff --git a/src/Stack.hs b/src/Stack.hs
new file mode 100644
--- /dev/null
+++ b/src/Stack.hs
@@ -0,0 +1,107 @@
+
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ExtendedDefaultRules #-}
+
+{- |
+
+Abstract over some of stack's functionality.
+
+-}
+
+module Stack
+where
+
+import Control.Monad.IO.Class     ( MonadIO(..) )
+import Shelly.Lifted
+import Data.Maybe
+import Data.Text                  ( Text )
+import qualified Data.Text as T
+import Prelude hiding (FilePath)
+import Control.Monad
+import Data.Char                  (isSpace)
+--import Control.Monad.Fail         ( MonadFail )
+--import Control.Monad.Trans.Maybe  (runMaybeT)
+
+
+-- | just a convenience alias, short for @'MonadIO' m, 'MonadSh' m,
+--  'MonadShControl' m
+type MonadShellish m = (MonadIO m, MonadSh m, MonadShControl m) 
+
+-- |  @haddockCanHyperlinkSrc@ tells you whether a version of
+-- haddock is on the path that can do source hyperlinking.
+--  
+-- (implementation: versions of haddock that can't do source hyperlinking
+-- return a non-zero status code if you pass "--hyperlinked-source".)
+haddockCanHyperlinkSrc :: MonadShellish m  => m Bool
+haddockCanHyperlinkSrc = errExit False $ do
+  silently $ run_ "haddock" ["--hyperlinked-source"]
+  (==0) <$> lastExitCode
+
+rstrip :: Text -> Text
+rstrip = T.dropWhileEnd isSpace
+
+-- | @onPath prog@ tells you whether the program
+-- is on the path
+onPath :: MonadSh f => FilePath -> f Bool
+onPath prog =
+  isJust <$> which prog
+
+-- | check that stack is on path, or die.
+stackIsOnPath :: MonadSh m => m ()
+stackIsOnPath = 
+  unlessM (onPath "stack") $ do
+    echo "Couldn't find stack on path - do you need to install stack?"
+    quietExit 1
+
+-- | check that ghc can be executed, using stack, or die.
+ghcIsOnPath :: MonadShellish m => m () 
+ghcIsOnPath = do
+  errExit False $ run_ "stack" ["exec", "--", "which", "ghc"]
+  exitCode <- lastExitCode 
+  when (exitCode /= 0) $
+    terror "Something is terribly wrong - couldn't get a path for ghc. exiting"
+
+
+-- | trim, convert, canonicalize a path given as Text
+-- (e.g. from 'run').
+extractPath :: MonadSh m => Text -> m FilePath
+extractPath =  canonicalize <=< (return . fromText . rstrip) 
+
+-- | get the directory containing ghc and haddock
+ghcDir :: MonadSh m => m FilePath
+ghcDir =
+  extractPath =<< run "stack" ["path", "--compiler-bin"]
+
+-- | get the snapshot binaries directory, where programs
+-- are installed
+snapshotBinDir :: MonadShellish m => m FilePath
+snapshotBinDir = 
+  (</> "bin") <$> (extractPath =<< silently (run "stack" ["path", "--snapshot-install-root"]))
+
+
+-- | whether "cabal" is installed in the current snapshot.
+cabalInstalled :: MonadShellish m => m Bool
+cabalInstalled = do
+  binDir <- snapshotBinDir 
+  sub $ do
+    setenv "PATH" (toTextIgnore binDir) 
+    onPath "cabal"
+
+-- | Install a @cabal@ executable, from the package @cabal-install@,
+-- into the package snapshot.
+installCabal :: MonadShellish m => m ()
+installCabal =  
+    verbosely $ run_ "stack" ["build", "--no-copy-bins", "cabal-install"]
+
+-- | prepend haddock's directory to the path
+addHaddockPath :: MonadSh m => m ()
+addHaddockPath = do
+  haddockDir <- extractPath =<< run "stack" ["path", "--compiler-bin"]
+  prependToPath haddockDir
+
+-- | prepend cabal's directory to the path
+addCabalPath :: MonadShellish m => m ()
+addCabalPath = 
+  snapshotBinDir >>= prependToPath
+
diff --git a/src/Upload.hs b/src/Upload.hs
--- a/src/Upload.hs
+++ b/src/Upload.hs
@@ -23,8 +23,11 @@
 doUpload :: String -> Upload -> Maybe Auth -> IO (Either String String)
 doUpload server upl userAuth = do
   req <- buildRequest server upl userAuth
-  displayResponse `liftM` sendRequest req
-
+  maybeResp <- sendRequest req
+  case maybeResp of
+    Left exception -> return $ Left $ 
+      "Request failed with a network exception: " ++ show exception
+    Right resp -> return $ displayResponse resp
 
 
 -- | Turn a 'Response' into some sort of hopefully useful error message
diff --git a/stack.yaml b/stack.yaml
--- a/stack.yaml
+++ b/stack.yaml
@@ -1,4 +1,4 @@
-resolver: lts-7.14
+resolver: lts-7.24
 #resolver: lts-3.7
 
 packages:
diff --git a/test/Distribution/Hup/Parse/Test.hs b/test/Distribution/Hup/Parse/Test.hs
deleted file mode 100644
--- a/test/Distribution/Hup/Parse/Test.hs
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-{- |
-
-Support code for testing Distribution.Hup.Parse 
-
--}
-module Distribution.Hup.Parse.Test where
-
-import Data.List                  (dropWhileEnd,isSuffixOf,stripPrefix
-                                  ,intercalate)
-import Data.ByteString.Lazy.Char8 (pack)
-import Test.QuickCheck
-
-import Distribution.Hup.Parse
-
-
-arbWord = do
-  len <- choose (1, 10)
-  vectorOf len $
-      oneof [choose ('a', 'z')
-           ,choose ('A', 'Z')]
-
-
-arbName :: Gen String
-arbName = do
-  len <- choose (1, 4)
-  intercalate "-" <$> vectorOf len arbWord
-
-arbVersion = do
-  numComponents <- choose (1,10)
-  numbers <- vectorOf numComponents $ getNonNegative <$> 
-                                      (arbitrary :: Gen (NonNegative Int))
-  return $ intercalate "." $ map show numbers 
-
--- Generate an Upload, including an empty file contents.
-arbUpload :: Gen Upload
-arbUpload = do
-  name   <- arbName
-  ver    <- arbVersion
-  isPack <- elements [IsPackage, IsDocumentation]
-  isCand <- elements [NormalPkg, CandidatePkg]
-  let pk   = Package name ver
-      file = name ++ "-" ++ ver ++ 
-                if isPack == IsPackage
-                then ".tar.gz"
-                else "-docs.tar.gz"
-  return $ Upload pk file (Just $ pack "") isPack isCand
-
-prop_parseTgzFilename_roundtripsOK  = 
-  forAll arbUpload $ \upl -> 
-    let 
-         parsed :: Either String (IsDocumentation, Package)  
-         parsed = (parseTgzFilename' $ fileToUpload upl )  
-
-    in case parsed of
-            Right (isDoc, Package parsedName parsedVer) -> 
-                  isDoc       == uploadType upl 
-              &&  parsedName  == packageName    ( package upl)
-              &&  parsedVer   == packageVersion ( package upl)
-            Left  msg -> 
-                  False
-
-
-
diff --git a/test/Distribution/Hup/ParseSpec.hs b/test/Distribution/Hup/ParseSpec.hs
deleted file mode 100644
--- a/test/Distribution/Hup/ParseSpec.hs
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-module Distribution.Hup.ParseSpec where
-
-import Test.Hspec
-import Test.QuickCheck
-
-import Distribution.Hup.Parse
-
-import Data.List                  (dropWhileEnd,isSuffixOf,stripPrefix
-                                  ,intercalate)
-import Data.ByteString.Lazy.Char8 (pack)
-
-import qualified Distribution.Hup.Parse.Test
-
-arbWord =  Distribution.Hup.Parse.Test.arbWord 
-
-prop_parseTgzFilename_roundtripsOK =
-  Distribution.Hup.Parse.Test.prop_parseTgzFilename_roundtripsOK
-
-
--- `main` is here so that this module can be run from GHCi on its own.  It is
--- not needed for automatic spec discovery.
-main :: IO ()
-main = hspec spec
-
-spec :: Spec
-spec = 
-  describe "parseTgzFilename" $ 
-    it "should round-trip back to original name, version, package type"  
-      prop_parseTgzFilename_roundtripsOK
-
-
diff --git a/test/Distribution/Hup/Upload/Test.hs b/test/Distribution/Hup/Upload/Test.hs
deleted file mode 100644
--- a/test/Distribution/Hup/Upload/Test.hs
+++ /dev/null
@@ -1,100 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE CPP #-}
-
-{- | Support for testing  Distribution.Hup.Upload 
-
--}
-
-module Distribution.Hup.Upload.Test where
-
-import qualified Data.ByteString.Lazy.Char8 as LBS   --   pack
-import qualified Network.HTTP.Client as HTTP.Client
-import System.FilePath              ( (</>) )
-import System.IO.Temp               (withSystemTempDirectory)
-import Test.QuickCheck
-import Test.QuickCheck.Monadic      (run, assert, monadicIO)
-
-import Distribution.Hup.Upload  
-import Distribution.Hup.Parse 
-import Distribution.Hup.Parse.Test
-
-type ParsedTgz = Either String (IsDocumentation, Package) 
-
-arbAuth =
-  mkAuth <$> arbitrary <*> arbitrary
-
-
--- | Round-trips an http request to check things seem to be going to the
--- right URLs.
---
--- Doesn't check the file/body, just metadata.
-httpRoundTripsOK'
-  :: (HTTP.Client.Request -> IO Response)
-     -> Int -> Property
-httpRoundTripsOK' sendRequest port = 
-  forAll arbUpload $ \upl ->
-    forAll arbAuth $ \auth ->
-      httpRoundTripsOK  sendRequest port upl auth
-
-
-httpRoundTripsOK sendRequest port upl auth = 
-      monadicIO $ do
-        response <- run $ emptyFileRequest port upl auth
-        assert $ statusCode response == 200
-
-        let bod = LBS.unpack $ responseBody response
-            _unserialized :: (IsDocumentation, IsCandidate, ParsedTgz)
-            _unserialized@(recdIsDoc1, recdIsCand, parsedTgz) = read bod
-
-        let sentIsCand = isCandidate upl
-            sentIsDoc  = uploadType  upl
-            sentPkg    = package     upl 
-
-        assert (parsedTgz == Right (sentIsDoc, sentPkg) )
-        assert (sentIsCand == recdIsCand)
-        assert (sentIsDoc  == recdIsDoc1)
-  where
-  emptyFileRequest :: Int -> Upload -> Maybe Auth -> IO Response
-  emptyFileRequest port upl auth = 
-    withSystemTempDirectory "huptest" $ \tmpDir -> do
-      let newFile = tmpDir </> fileToUpload upl
-      upl <- return $ upl { fileToUpload = newFile } 
-      writeFile (tmpDir </> fileToUpload upl) ""
-      let url = "http://localhost:" ++ show port ++ "/"
-      buildRequest url upl auth >>= sendRequest
-
-
--- | Round-trips an http request to check things seem to be going to the
--- right URLs.
---
--- Doesn't check the file/body, just metadata.
-badUrlReturns'
-  :: (HTTP.Client.Request -> IO Response)
-     -> Int -> Property
-badUrlReturns' sendRequest port = 
-  forAll arbUpload $ \upl ->
-    forAll arbAuth $ \auth ->
-      badUrlReturns sendRequest port upl auth
-
-
-
--- | Given a bad url, the http library should return a 
--- non-2XX status code, rather than throwing an exception.
-badUrlReturns sendRequest port upl auth = do
-  monadicIO $ do
-    response <- run $ badRequest port upl auth
-    assert $ statusCode response /= 200
-
-  where
-  badRequest :: Int -> Upload -> Maybe Auth -> IO Response
-  badRequest port upl auth = 
-    withSystemTempDirectory "huptest" $ \tmpDir -> do
-      let newFile = tmpDir </> (fileToUpload upl)
-      upl <- return $ upl { fileToUpload = newFile } 
-      writeFile (tmpDir </> (fileToUpload upl)) ""
-      let url = "http://localhost:" ++ show port ++ "/fubar/"
-      buildRequest url upl auth >>= sendRequest
-
-
-
-
diff --git a/test/Distribution/Hup/UploadSpec.hs b/test/Distribution/Hup/UploadSpec.hs
deleted file mode 100644
--- a/test/Distribution/Hup/UploadSpec.hs
+++ /dev/null
@@ -1,176 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Distribution.Hup.UploadSpec where
-
-
-
-
-import Control.Exception                      (throwIO)
-import Control.Monad
-import Control.Monad.IO.Class                 (liftIO)
-import qualified Data.ByteString.Char8 as BS
-import qualified Data.ByteString.Lazy.Char8 as LBS   --   pack
-import Data.Maybe                             (fromJust)
-import Data.Monoid                            ( (<>) )
-import Network.HTTP.Client.MultipartFormData  (renderParts,webkitBoundary)
-import Network.HTTP.Types as T                (statusCode,methodPost
-                                              ,StdMethod(..)) 
-import Network.Wai.Parse as Parse             (FileInfo(..), fileName)
-import Network.Wai.Test                       (simpleStatus,SResponse
-                                              ,simpleBody)
-import Test.Hspec
-import qualified Test.Hspec.Wai as HWai       --(put, request)
-import Test.Hspec.Wai.Internal                --(WaiSession,runWaiSession)
-import Test.QuickCheck                        --(forAll)
-import Test.QuickCheck.Monadic                --(assert, run, monadicIO )
-import Web.Frank                              --(post, put)
-import Web.Simple --(Application,controllerApp, ControllerT,Controller,ok,parseForm, queryParam, rawPathInfo, request, respond, routeMethod, routePattern)
-
-
-import Distribution.Hup.Upload  
-import Distribution.Hup.Parse 
-import Distribution.Hup.Parse.Test
-
-import qualified Distribution.Hup.Upload.Test 
-
-import qualified Distribution.Hup.WebTest 
-
-type ParsedTgz = Either String (IsDocumentation, Package) 
-
-
--- `main` is here so that this module can be run from GHCi on its own.  It is
--- not needed for automatic spec discovery.
-main :: IO ()
-main = 
-  hspec spec 
-
-spec :: Spec
-spec = do
-  describe "testing with mocked requests" $ 
-    describe "mocked requests" $
-      context "when processed by a mock hackage server" $ 
-        it "should go to the right web app path" 
-          httpMetadataRoundtripsOK'  
-  describe "testing with live HTTP requests" $ 
-    -- this will be replaced with a stub unless the WEB_TESTS macro
-    -- is defined.
-    Distribution.Hup.WebTest.liveTest webApp
-
-
-
-httpMetadataRoundtripsOK' = 
-  forAll arbUpload $ \upl -> httpMetadataRoundtripsOK upl 
-
-
-httpMetadataRoundtripsOK upl = monadicIO $ do 
-  upl <- return $ upl { fileConts = Just "" }
-  testRequest <- run $ buildTestRequest "" upl 
-  testResponse <- run $ sendTestRequest testRequest
-
-  let resStatus = T.statusCode $ simpleStatus testResponse
-      resBody :: String
-      resBody =   LBS.unpack $ simpleBody testResponse
-      _unserialized :: (IsDocumentation, IsCandidate, ParsedTgz)
-      _unserialized@(recdIsDoc1, recdIsCand, parsedTgz) = read resBody
-
-  let sentIsCand = isCandidate upl
-      sentIsDoc  = uploadType  upl
-      sentPkg    = package     upl
-
-  assert (resStatus == 200)
-  assert (sentIsCand == recdIsCand)
-  assert (sentIsDoc  == recdIsDoc1)
-  assert (parsedTgz == Right (sentIsDoc, sentPkg) )
-
-
-sendTestRequest :: WaiSession SResponse -> IO SResponse
-sendTestRequest testReq =   
-  runWaiSession testReq webApp
-
---showInd x =
---  indent 4 (show x) 
---
---indent n = 
---  let wspace = replicate n ' ' 
---  in  unlines .  (map (wspace ++)) . lines
-
-testPut :: String -> LBS.ByteString -> WaiSession SResponse
-testPut url conts = 
-  HWai.put (BS.pack url) conts
-
-testPost
-  :: String -> FilePath -> LBS.ByteString -> IO (WaiSession SResponse)
-testPost url fileName fileConts = do
-  boundary <- webkitBoundary 
-  let part    = mkPart fileName fileConts
-      headers = [("Content-Type", 
-                    "multipart/form-data; boundary=" <> boundary)]
-  body <- bodyToByteString <$> renderParts boundary [part]
-  return $ HWai.request T.methodPost (BS.pack url) headers body
-
-
--- Only call when fileConts has something in it.
-buildTestRequest
-  :: String -> Upload -> IO (WaiSession SResponse)
-buildTestRequest serverUrl upl  = do
-  let (Upload _ filePath fileConts uploadType _pkgType ) = upl
-      url = getUploadUrl serverUrl upl
-  fileConts <- return (fromJust fileConts)
-  case uploadType of
-      IsPackage -> 
-         testPost url filePath fileConts
-      IsDocumentation -> 
-         return $ testPut url fileConts 
-
-
-ioAssert pred mesg = 
-  unless pred $
-        liftIO $ throwIO $ userError mesg
-
--- | We send the parsed results back as plain text,
--- parseable by 'Text.Read.read'.
-webApp :: Application
-webApp = controllerApp () $ do
-    myReq <- request 
-    routeMethod T.POST $ do path <- rawPathInfo <$> request
-                            let isCand = if "/candidates/" `BS.isSuffixOf`path
-                                         then CandidatePkg
-                                         else NormalPkg  
-                            when (path == "/packages/" || 
-                                 path == "/packages/candidates/") $ do
-                                    (_params, files) <- parseForm
-                                    handlePost isCand files
-    routeMethod T.PUT $ routePattern "/package/:pkgVer/:isCand" $ do
-                            pkgVer <- fromJust <$> queryParam "pkgVer"  
-                            let filename = pkgVer :: String
-                            isCand <- fromJust <$> queryParam "isCand" 
-                            let isCand' = if ("candidate" :: String) == isCand
-                                         then CandidatePkg
-                                         else NormalPkg
-                            let remainingBit = if "candidate"== isCand
-                                          then "docs"
-                                          else ""
-                            routePattern remainingBit $ 
-                              handlePut isCand' filename
-  where 
-    handlePost :: IsCandidate -> [(a, FileInfo c)] -> ControllerT s IO b
-    handlePost isCand files = do
-      ioAssert (length files == 1)
-               "posted package should have exactly 1 file" 
-      let filename = BS.unpack $ Parse.fileName $ snd $ head files
-          parsed :: Either String (IsDocumentation, Package)  
-          parsed = parseTgzFilename' filename
-      respond $ ok "text/plain" $ LBS.pack $ show (IsPackage, isCand, parsed)
-
-    handlePut :: IsCandidate -> FilePath -> Controller s a
-    handlePut isCand filename = do
-      let parsed :: Either String (IsDocumentation, Package)  
-          parsed = parseTgzFilename' (filename ++ "-docs.tar.gz")
-      respond $ ok "text/plain" $ LBS.pack $ 
-                show (IsDocumentation, isCand, parsed)
-
-
-
-
-
-
diff --git a/test/Distribution/Hup/WebTest.hs b/test/Distribution/Hup/WebTest.hs
deleted file mode 100644
--- a/test/Distribution/Hup/WebTest.hs
+++ /dev/null
@@ -1,70 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE CPP #-}
-
-module Distribution.Hup.WebTest where
-
-
-import Distribution.Hup.Upload  
-import Distribution.Hup.Upload.Test
-import Distribution.Hup.Parse 
-import Network.Wai                            (Application)
-import Test.Hspec
-import Test.QuickCheck
-
-#ifdef WEB_TESTS
-
-import Control.Concurrent                     (forkIO, ThreadId)
-import qualified Network.Socket as Soc        (Socket, close)
-import Network.Wai.Handler.Warp               (Port, defaultSettings
-                                              ,runSettingsSocket)
-
-
-#if MIN_VERSION_warp(3,2,4)
-import qualified Network.Wai.Handler.Warp as Warp  (openFreePort)
-
-openFreePort = Warp.openFreePort
-#else
-import Network.Socket
-
-openFreePort :: IO (Port, Soc.Socket)
-openFreePort = do 
-  s <- socket AF_INET Stream defaultProtocol
-  localhost <- inet_addr "127.0.0.1"
-  bind s (SockAddrInet aNY_PORT localhost)
-  listen s 1
-  port <- socketPort s
-  return (fromIntegral port, s)
-#endif
-
-startServer :: Application -> IO (Port, Soc.Socket, ThreadId)
-startServer webApp = do
-  (port, sock) <- openFreePort 
-  tid <- forkIO $ runSettingsSocket defaultSettings sock webApp
-  return (port, sock, tid) 
-
-shutdownServer :: (Port, Soc.Socket, ThreadId) -> IO ()
-shutdownServer (_port, sock, _tid) = 
-  Soc.close sock
-
-liveTest :: Application -> SpecWith ()
-liveTest webApp = do
-    beforeAll (startServer webApp) $ afterAll shutdownServer $ 
-      describe "buildRequest" $ do
-        context "when its result is fed into sendRequest" $ 
-          it "should send to the right web app path" $ \(port, sock, tid) -> 
-            httpRoundTripsOK' sendRequest port 
-
-        context "when given a bad URL" $ 
-          it "should not throw an exception" $ \(port, sock, tid) -> 
-            badUrlReturns' sendRequest port 
-
-#else
-
-liveTest :: Application -> SpecWith ()
-liveTest _ = return ()
-
-#endif
-
-
-
-
diff --git a/test/DocTest.hs b/test/DocTest.hs
deleted file mode 100644
--- a/test/DocTest.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-
-import System.FilePath.Glob (glob)
-import Test.DocTest (doctest)
-
-main :: IO ()
-main = glob "lib/**/*.hs" >>= doctest
-
diff --git a/test/Spec.hs b/test/Spec.hs
deleted file mode 100644
--- a/test/Spec.hs
+++ /dev/null
@@ -1,1 +0,0 @@
-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
