summaryrefslogtreecommitdiff
path: root/Command/TestRemote.hs
diff options
context:
space:
mode:
Diffstat (limited to 'Command/TestRemote.hs')
-rw-r--r--Command/TestRemote.hs196
1 files changed, 196 insertions, 0 deletions
diff --git a/Command/TestRemote.hs b/Command/TestRemote.hs
new file mode 100644
index 000000000..29a2e809c
--- /dev/null
+++ b/Command/TestRemote.hs
@@ -0,0 +1,196 @@
+{- git-annex command
+ -
+ - Copyright 2014 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+module Command.TestRemote where
+
+import Common
+import Command
+import qualified Annex
+import qualified Remote
+import qualified Types.Remote as Remote
+import Types
+import Types.Key (key2file, keyBackendName, keySize)
+import Types.Backend (getKey, fsckKey)
+import Types.KeySource
+import Annex.Content
+import Backend
+import qualified Backend.Hash
+import Utility.Tmp
+import Utility.Metered
+import Utility.DataUnits
+import Utility.CopyFile
+import Messages
+import Types.Messages
+import Remote.Helper.Chunked
+import Locations
+
+import Test.Tasty
+import Test.Tasty.Runners
+import Test.Tasty.HUnit
+import Control.Exception
+import "crypto-api" Crypto.Random
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
+import qualified Data.Map as M
+
+def :: [Command]
+def = [ withOptions [sizeOption] $
+ command "testremote" paramRemote seek SectionTesting
+ "test transfers to/from a remote"]
+
+sizeOption :: Option
+sizeOption = fieldOption [] "size" paramSize "base key size (default 1MiB)"
+
+seek :: CommandSeek
+seek ps = do
+ basesz <- fromInteger . fromMaybe (1024 * 1024)
+ <$> getOptionField sizeOption (pure . getsize)
+ withWords (start basesz) ps
+ where
+ getsize v = v >>= readSize dataUnits
+
+start :: Int -> [String] -> CommandStart
+start basesz ws = do
+ let name = unwords ws
+ showStart "testremote" name
+ r <- either error id <$> Remote.byName' name
+ showSideAction "generating test keys"
+ ks <- mapM randKey (keySizes basesz)
+ rs <- catMaybes <$> mapM (adjustChunkSize r) (chunkSizes basesz)
+ rs' <- concat <$> mapM encryptionVariants rs
+ next $ perform rs' ks
+
+perform :: [Remote] -> [Key] -> CommandPerform
+perform rs ks = do
+ st <- Annex.getState id
+ let tests = testGroup "Remote Tests" $
+ [ testGroup (desc r k) (test st r k) | k <- ks, r <- rs ]
+ ok <- case tryIngredients [consoleTestReporter] mempty tests of
+ Nothing -> error "No tests found!?"
+ Just act -> liftIO act
+ next $ cleanup rs ks ok
+ where
+ desc r' k = intercalate "; " $ map unwords
+ [ [ "key size", show (keySize k) ]
+ , [ show (chunkConfig (Remote.config r')) ]
+ , ["encryption", fromMaybe "none" (M.lookup "encryption" (Remote.config r'))]
+ ]
+
+adjustChunkSize :: Remote -> Int -> Annex (Maybe Remote)
+adjustChunkSize r chunksize = adjustRemoteConfig r
+ (M.insert "chunk" (show chunksize))
+
+-- Variants of a remote with no encryption, and with simple shared
+-- encryption. Gpg key based encryption is not tested.
+encryptionVariants :: Remote -> Annex [Remote]
+encryptionVariants r = do
+ noenc <- adjustRemoteConfig r (M.insert "encryption" "none")
+ sharedenc <- adjustRemoteConfig r $
+ M.insert "encryption" "shared" .
+ M.insert "highRandomQuality" "false"
+ return $ catMaybes [noenc, sharedenc]
+
+-- Regenerate a remote with a modified config.
+adjustRemoteConfig :: Remote -> (Remote.RemoteConfig -> Remote.RemoteConfig) -> Annex (Maybe Remote)
+adjustRemoteConfig r adjustconfig = Remote.generate (Remote.remotetype r)
+ (Remote.repo r)
+ (Remote.uuid r)
+ (adjustconfig (Remote.config r))
+ (Remote.gitconfig r)
+
+test :: Annex.AnnexState -> Remote -> Key -> [TestTree]
+test st r k =
+ [ check "removeKey when not present" remove
+ , present False
+ , check "storeKey" store
+ , present True
+ , check "storeKey when already present" store
+ , present True
+ , check "retrieveKeyFile" $ do
+ removeAnnex k
+ get
+ , check "fsck downloaded object" fsck
+ , check "retrieveKeyFile resume from 33%" $ do
+ loc <- Annex.calcRepo (gitAnnexLocation k)
+ tmp <- prepTmp k
+ partial <- liftIO $ bracket (openBinaryFile loc ReadMode) hClose $ \h -> do
+ sz <- hFileSize h
+ L.hGet h $ fromInteger $ sz `div` 3
+ liftIO $ L.writeFile tmp partial
+ removeAnnex k
+ get
+ , check "fsck downloaded object" fsck
+ , check "retrieveKeyFile resume from 0" $ do
+ tmp <- prepTmp k
+ liftIO $ writeFile tmp ""
+ removeAnnex k
+ get
+ , check "fsck downloaded object" fsck
+ , check "retrieveKeyFile resume from end" $ do
+ loc <- Annex.calcRepo (gitAnnexLocation k)
+ tmp <- prepTmp k
+ void $ liftIO $ copyFileExternal loc tmp
+ removeAnnex k
+ get
+ , check "fsck downloaded object" fsck
+ , check "removeKey when present" remove
+ , present False
+ ]
+ where
+ check desc a = testCase desc $
+ Annex.eval st (Annex.setOutput QuietOutput >> a) @? "failed"
+ present b = check ("present " ++ show b) $
+ (== Right b) <$> Remote.hasKey r k
+ fsck = case maybeLookupBackendName (keyBackendName k) of
+ Nothing -> return True
+ Just b -> case fsckKey b of
+ Nothing -> return True
+ Just fscker -> fscker k (key2file k)
+ get = getViaTmp k $ \dest ->
+ Remote.retrieveKeyFile r k Nothing dest nullMeterUpdate
+ store = Remote.storeKey r k Nothing nullMeterUpdate
+ remove = Remote.removeKey r k
+
+cleanup :: [Remote] -> [Key] -> Bool -> CommandCleanup
+cleanup rs ks ok = do
+ forM_ rs $ \r -> forM_ ks (Remote.removeKey r)
+ forM_ ks removeAnnex
+ return ok
+
+chunkSizes :: Int -> [Int]
+chunkSizes base =
+ [ 0 -- no chunking
+ , base `div` 100
+ , base `div` 1000
+ , base
+ ]
+
+keySizes :: Int -> [Int]
+keySizes base = filter (>= 0)
+ [ 0 -- empty key is a special case when chunking
+ , base
+ , base + 1
+ , base - 1
+ , base * 2
+ ]
+
+randKey :: Int -> Annex Key
+randKey sz = withTmpFile "randkey" $ \f h -> do
+ gen <- liftIO (newGenIO :: IO SystemRandom)
+ case genBytes sz gen of
+ Left e -> error $ "failed to generate random key: " ++ show e
+ Right (rand, _) -> liftIO $ B.hPut h rand
+ liftIO $ hClose h
+ let ks = KeySource
+ { keyFilename = f
+ , contentLocation = f
+ , inodeCache = Nothing
+ }
+ k <- fromMaybe (error "failed to generate random key")
+ <$> getKey Backend.Hash.testKeyBackend ks
+ moveAnnex k f
+ return k