packages feed

flac-0.1.0: tests/Codec/Audio/FLAC/StreamEncoderSpec.hs

--
-- Test suite for stream encoder.
--
-- Copyright © 2016–2017 Mark Karpov <markkarpov@openmailbox.org>
--
-- 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 Mark Karpov nor the names of 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 “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 THE COPYRIGHT HOLDERS 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.

{-# LANGUAGE RecordWildCards #-}

module Codec.Audio.FLAC.StreamEncoderSpec
  ( spec )
where

import Codec.Audio.FLAC.StreamDecoder
import Codec.Audio.FLAC.StreamEncoder
import Codec.Audio.Wave
import Control.Monad
import Data.ByteString (ByteString)
import Data.Default.Class
import System.Directory
import System.FilePath
import System.IO
import System.IO.Temp (withSystemTempDirectory)
import Test.Hspec
import qualified Data.ByteString as B

-- NOTE OK, we test here a lot of things at the same time, which may
-- disappoint adepts of various testing techniques and principles, but it's
-- easier to test many things simultaneously in this case, and when
-- something is not right, it's usually obvious what's wrong.

spec :: Spec
spec =
  describe "encodeFlac and decodeFlac" $
    withVariousWaves $
      it "encodes and decodes back correctly" $ \path -> do
        -- Input and output paths coincide on purpose, we test that nothing
        -- bad can happen in this case.
        old <- Blind <$> fetchWaveBody path
        -- We first let the built-in verification mechanics catch possible
        -- errors.
        encodeFlac def { encoderVerify = True } path path
        -- Then we let decoder check that the streams match with MD5 hash.
        decodeFlac def { decoderMd5Checking = True } path path
        -- But we also want to be sure that audio streams match
        -- byte-by-byte, we can't trust just FLAC's own checking.
        new <- Blind <$> fetchWaveBody path
        new `shouldBe` old

----------------------------------------------------------------------------
-- Helpers

newtype Blind a = Blind a

instance Eq a => Eq (Blind a) where
  (Blind x) == (Blind y) = x == y

instance Show (Blind a) where
  show _ = "<blind data cannot be shown>"

-- | Run given test with various WAVE files.

withVariousWaves :: SpecWith FilePath -> SpecWith ()
withVariousWaves m =
  forM_ waveFiles $ \(path, desc) ->
    context ("when given " ++ desc) (around (withSandbox path) m)

-- | Make a temporary copy of given file and provide the path to the file in
-- a sandbox directory. Automatically remove the files when the test
-- finishes.

withSandbox :: FilePath -> ActionWith FilePath -> IO ()
withSandbox path action =
  withSystemTempDirectory "flac-test" $ \dir -> do
    let path' = dir </> "файл" -- testing Unicode
    copyFile path path'
    action path'

-- | Extract body of given WAVE or RF64 file as a strict 'ByteString'.
-- Needless to say that the body should be relatively short.

fetchWaveBody :: FilePath -> IO ByteString
fetchWaveBody path = do
  Wave {..} <- readWaveFile path
  withBinaryFile path ReadMode $ \h -> do
    hSeek h AbsoluteSeek (fromIntegral waveDataOffset)
    B.hGet h (fromIntegral waveDataSize)

waveFiles :: [(FilePath, String)]
waveFiles =
  [ ( "audio-samples/1ch-44100hz-16bit.rf64"
    , "1 channel 44100 Hz 16 bit (RF64)" )
  , ( "audio-samples/1ch-44100hz-16bit.wav"
    , "1 channel 44100 Hz 16 bit (WAVE)" )
  , ( "audio-samples/1ch-44100hz-16bit-x.wav"
    , "1 channel 44100 Hz 16 bit (WAVEX)" )
  , ( "audio-samples/2ch-11025hz-24bit.rf64"
    , "2 channels 11025 Hz 24 bit (RF64)" )
  , ( "audio-samples/2ch-11025hz-24bit.wav"
    , "2 channels 11025 Hz 24 bit (WAVE)" )
  , ( "audio-samples/2ch-11025hz-24bit-x.wav"
    , "2 channels 11025 Hz 24 bit (WAVEX)" )
  , ( "audio-samples/2ch-8000hz-8bit.rf64"
    , "2 channels 8000 Hz 8 bit (RF64)" )
  , ( "audio-samples/2ch-8000hz-8bit.wav"
    , "2 channels 8000 Hz 8 bit (WAVE)" )
  , ( "audio-samples/2ch-8000hz-8bit-x.wav"
    , "2 channels 8000 Hz 8 bit (WAVEX)" )
  , ( "audio-samples/1ch-44100hz-8bit-full-range.wav"
    , "1 channel 44100 Hz 8 bit full range (WAVE)" )
  , ( "audio-samples/1ch-44100hz-8bit-silence.wav"
    , "1 channel 44100 Hz 8 bit silence (WAVE)" )
  , ( "audio-samples/1ch-44100hz-16bit-full-range.wav"
    , "1 channel 44100 Hz 16 bit full range (WAVE)" )
  , ( "audio-samples/1ch-44100hz-16bit-silence.wav"
    , "1 channel 44100 Hz 16 bit silence (WAVE)" )
  , ( "audio-samples/1ch-44100hz-24bit-full-range.wav"
    , "1 channel 44100 Hz 24 bit full range (WAVE)" )
  , ( "audio-samples/1ch-44100hz-24bit-silence.wav"
    , "1 channel 44100 Hz 24 bit silence (WAVE)" )
  ]