cue-sheet-2.0.2: tests/Text/CueSheet/ParserSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Text.CueSheet.ParserSpec (spec) where
import Control.Monad.Catch
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.List.NonEmpty as NE
import Test.Hspec
import Test.Hspec.Megaparsec
import Text.CueSheet.Parser
import Text.CueSheet.Types
import Text.Megaparsec
spec :: Spec
spec =
describe "parseCueSheet" $ do
it "correctly deals with whitespace" $ do
bs <- B.readFile "cue-sheet-samples/parser-whitespace.cue"
sheet <- testSheet
parseCueSheet "" bs `shouldParse` sheet
it "parses commands case-insensitively" $ do
bs <- B.readFile "cue-sheet-samples/parser-case-insensitivity.cue"
sheet <- testSheet
parseCueSheet "" bs `shouldParse` sheet
it "handles comments properly" $ do
bs <- B.readFile "cue-sheet-samples/parser-comments.cue"
sheet <- testSheet
parseCueSheet "" bs `shouldParse` sheet
it "at least one file should be declared" $ do
bs <- B.readFile "cue-sheet-samples/parser-missing-file.cue"
let es =
foldMap
etoks
[ "CATALOG",
"CDTEXTFILE",
"FILE",
"PERFORMER",
"REM",
"SONGWRITER",
"TITLE"
]
parseCueSheet "" bs
`shouldFailWith` err 67 (ueof <> es)
it "at least one track should be declared" $ do
bs <- B.readFile "cue-sheet-samples/parser-missing-track.cue"
parseCueSheet "" bs
`shouldFailWith` err 109 (ueof <> etoks "REM" <> etoks "TRACK")
it "at least one non-zero index should be declared" $ do
bs <- B.readFile "cue-sheet-samples/parser-missing-index.cue"
let e = wrappedTrivial (ueof <> etoks "INDEX" <> etoks "REM")
parseCueSheet "" bs
`shouldFailWith` errFancy 174 (cstm (Eec (Just 1) e))
it "parses a normal CUE file without issues" $ do
bs <- B.readFile "cue-sheet-samples/parser-normal.cue"
sheet <- normalCueSheet
parseCueSheet "" bs `shouldParse` sheet
it "rejects invalid catalog number" $ do
bs <- B.readFile "cue-sheet-samples/parser-invalid-catalog.cue"
parseCueSheet "" bs
`shouldFailWith` errFancy
8
(cstm (Eec Nothing (CueParserInvalidCatalog "123")))
it "rejects invalid CUE text" $ do
bs <- B.readFile "cue-sheet-samples/parser-invalid-text.cue"
parseCueSheet "" bs
`shouldFailWith` errFancy
10
(cstm (Eec Nothing (CueParserInvalidCueText "")))
it "detects and reports tracks that appear out of order" $ do
bs <- B.readFile "cue-sheet-samples/parser-track-out-of-order.cue"
parseCueSheet "" bs
`shouldFailWith` errFancy
182
(cstm (Eec Nothing CueParserTrackOutOfOrder))
it "rejects invalid ISRC values" $ do
bs <- B.readFile "cue-sheet-samples/parser-invalid-isrc.cue"
parseCueSheet "" bs
`shouldFailWith` errFancy
161
(cstm (Eec (Just 3) (CueParserInvalidTrackIsrc "123")))
it "rejects invalid number of seconds" $ do
bs <- B.readFile "cue-sheet-samples/parser-invalid-seconds.cue"
parseCueSheet "" bs
`shouldFailWith` errFancy
168
(cstm (Eec (Just 1) (CueParserInvalidSeconds 61)))
it "rejects invalid number of frames" $ do
bs <- B.readFile "cue-sheet-samples/parser-invalid-frames.cue"
parseCueSheet "" bs
`shouldFailWith` errFancy
171
(cstm (Eec (Just 1) (CueParserInvalidFrames 77)))
it "detects and reports indices that appear out of order" $ do
bs <- B.readFile "cue-sheet-samples/parser-index-out-of-order.cue"
parseCueSheet "" bs
`shouldFailWith` errFancy
162
(cstm (Eec (Just 1) CueParserTrackIndexOutOfOrder))
it "rejects duplicate CATALOG command" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-catalog.cue"
let es =
utoks "CATA"
<> foldMap
etoks
[ "CDTEXTFILE",
"FILE",
"PERFORMER",
"REM",
"SONGWRITER",
"TITLE"
]
parseCueSheet "" bs `shouldFailWith` err 22 es
it "rejects duplicate CDTEXTFILE command" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-cdtextfile.cue"
let es =
utoks "CDTE"
<> foldMap
etoks
[ "CATALOG",
"FILE",
"PERFORMER",
"REM",
"SONGWRITER",
"TITLE"
]
parseCueSheet "" bs `shouldFailWith` err 23 es
it "rejects duplicate PERFORMER command" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-performer.cue"
let es =
utoks "PERF"
<> foldMap
etoks
[ "CATALOG",
"CDTEXTFILE",
"FILE",
"REM",
"SONGWRITER",
"TITLE"
]
parseCueSheet "" bs `shouldFailWith` err 30 es
it "rejects duplicate TITLE command" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-title.cue"
let es =
utoks "TITL"
<> foldMap
etoks
[ "CATALOG",
"CDTEXTFILE",
"FILE",
"PERFORMER",
"REM",
"SONGWRITER"
]
parseCueSheet "" bs `shouldFailWith` err 17 es
it "rejects duplicate SONGWRITER command" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-songwriter.cue"
let es =
utoks "SONG"
<> foldMap
etoks
[ "CATALOG",
"CDTEXTFILE",
"FILE",
"PERFORMER",
"REM",
"TITLE"
]
parseCueSheet "" bs `shouldFailWith` err 18 es
it "rejects duplicate FLAGS command (in track)" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-flags.cue"
let es =
wrappedTrivial $
utoks "FLAGS"
<> foldMap
etoks
[ "INDEX",
"ISRC",
"PERFORMER",
"PREGAP",
"REM",
"SONGWRITER",
"TITLE"
]
parseCueSheet "" bs
`shouldFailWith` errFancy 144 (cstm (Eec (Just 1) es))
it "rejects duplicate ISRC command (in track)" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-isrc.cue"
let es =
wrappedTrivial $
utoks "ISRC "
<> foldMap
etoks
[ "FLAGS",
"INDEX",
"PERFORMER",
"PREGAP",
"REM",
"SONGWRITER",
"TITLE"
]
parseCueSheet "" bs
`shouldFailWith` errFancy 152 (cstm (Eec (Just 1) es))
it "rejects duplicate PERFORMER command (in track)" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-performer.cue"
let es =
wrappedTrivial $
utoks "PERFO"
<> foldMap
etoks
[ "FLAGS",
"INDEX",
"ISRC",
"PREGAP",
"REM",
"SONGWRITER",
"TITLE"
]
parseCueSheet "" bs
`shouldFailWith` errFancy 156 (cstm (Eec (Just 1) es))
it "rejects duplicate TITLE command (in track)" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-title.cue"
let es =
wrappedTrivial $
utoks "TITLE"
<> foldMap
etoks
[ "FLAGS",
"INDEX",
"ISRC",
"PERFORMER",
"PREGAP",
"REM",
"SONGWRITER"
]
parseCueSheet "" bs
`shouldFailWith` errFancy 146 (cstm (Eec (Just 1) es))
it "rejects duplicate SONGWRITER command (in track)" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-songwriter.cue"
let es =
wrappedTrivial $
utoks "SONGW"
<> foldMap
etoks
[ "FLAGS",
"INDEX",
"ISRC",
"PERFORMER",
"PREGAP",
"REM",
"TITLE"
]
parseCueSheet "" bs
`shouldFailWith` errFancy 150 (cstm (Eec (Just 1) es))
it "rejects duplicate PREGAP command (in track)" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-pregap.cue"
let es =
wrappedTrivial $
utoks "PREGA"
<> foldMap
etoks
[ "FLAGS",
"INDEX",
"ISRC",
"PERFORMER",
"REM",
"SONGWRITER",
"TITLE"
]
parseCueSheet "" bs
`shouldFailWith` errFancy 176 (cstm (Eec (Just 1) es))
it "rejects duplicate POSTGAP command (in track)" $ do
bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-postgap.cue"
let es =
foldMap
etoks
[ "FILE",
"REM",
"TRACK"
]
parseCueSheet "" bs
`shouldFailWith` err 199 (utok 80 <> es <> eeof)
testSheet :: MonadThrow m => m CueSheet
testSheet = do
performer <- mkCueText "Faithless"
title <- mkCueText "Live in Berlin"
trackTitle <- mkCueText "Reverence"
return
CueSheet
{ cueCatalog = Nothing,
cueCdTextFile = Nothing,
cuePerformer = Just performer,
cueTitle = Just title,
cueSongwriter = Nothing,
cueFirstTrackNumber = 1,
cueFiles =
NE.fromList
[ CueFile
{ cueFileName = "Faithless - Live in Berlin.mp3",
cueFileType = MP3,
cueFileTracks =
NE.fromList
[ CueTrack
{ cueTrackDigitalCopyPermitted = False,
cueTrackFourChannelAudio = False,
cueTrackPreemphasisEnabled = False,
cueTrackSerialCopyManagement = False,
cueTrackType = CueTrackAudio,
cueTrackIsrc = Nothing,
cueTrackTitle = Just trackTitle,
cueTrackPerformer = Just performer,
cueTrackSongwriter = Nothing,
cueTrackPregap = Nothing,
cueTrackPregapIndex = Nothing,
cueTrackIndices =
NE.fromList
[CueTime 3, CueTime 151],
cueTrackPostgap = Nothing
}
]
}
]
}
normalCueSheet :: MonadThrow m => m CueSheet
normalCueSheet = do
performer <- mkCueText "Faithless"
catalog <- mkMcn "1112223334445"
title <- mkCueText "Live in Berlin"
track1Title <- mkCueText "Reverence"
track2Title <- mkCueText "She's My Baby"
track3Title <- mkCueText "Take the Long Way Home"
track4Title <- mkCueText "Insomnia"
track5Title <- mkCueText "Bring the Family Back"
track6Title <- mkCueText "Salva Mea"
track7Title <- mkCueText "Dirty Old Man"
track8Title <- mkCueText "God Is a DJ"
return
CueSheet
{ cueCatalog = Just catalog,
cueCdTextFile = Just "blah-blah.txt",
cuePerformer = Just performer,
cueTitle = Just title,
cueSongwriter = Nothing,
cueFirstTrackNumber = 1,
cueFiles =
NE.fromList
[ CueFile
{ cueFileName = "Faithless - Live in Berlin.mp3",
cueFileType = MP3,
cueFileTracks =
NE.fromList
[ CueTrack
{ cueTrackDigitalCopyPermitted = False,
cueTrackFourChannelAudio = False,
cueTrackPreemphasisEnabled = False,
cueTrackSerialCopyManagement = False,
cueTrackType = CueTrackAudio,
cueTrackIsrc = Nothing,
cueTrackTitle = Just track1Title,
cueTrackPerformer = Just performer,
cueTrackSongwriter = Nothing,
cueTrackPregap = Just (CueTime 75),
cueTrackPregapIndex = Nothing,
cueTrackIndices = NE.fromList [CueTime 0],
cueTrackPostgap = Nothing
},
CueTrack
{ cueTrackDigitalCopyPermitted = False,
cueTrackFourChannelAudio = False,
cueTrackPreemphasisEnabled = False,
cueTrackSerialCopyManagement = False,
cueTrackType = CueTrackAudio,
cueTrackIsrc = Nothing,
cueTrackTitle = Just track2Title,
cueTrackPerformer = Just performer,
cueTrackSongwriter = Nothing,
cueTrackPregap = Nothing,
cueTrackPregapIndex = Nothing,
cueTrackIndices = NE.fromList [CueTime 30150],
cueTrackPostgap = Nothing
},
CueTrack
{ cueTrackDigitalCopyPermitted = False,
cueTrackFourChannelAudio = False,
cueTrackPreemphasisEnabled = False,
cueTrackSerialCopyManagement = False,
cueTrackType = CueTrackAudio,
cueTrackIsrc = Nothing,
cueTrackTitle = Just track3Title,
cueTrackPerformer = Just performer,
cueTrackSongwriter = Nothing,
cueTrackPregap = Nothing,
cueTrackPregapIndex = Nothing,
cueTrackIndices = NE.fromList [CueTime 49050],
cueTrackPostgap = Just (CueTime 76)
},
CueTrack
{ cueTrackDigitalCopyPermitted = False,
cueTrackFourChannelAudio = False,
cueTrackPreemphasisEnabled = False,
cueTrackSerialCopyManagement = False,
cueTrackType = CueTrackAudio,
cueTrackIsrc = Nothing,
cueTrackTitle = Just track4Title,
cueTrackPerformer = Just performer,
cueTrackSongwriter = Nothing,
cueTrackPregap = Nothing,
cueTrackPregapIndex = Nothing,
cueTrackIndices =
NE.fromList
[CueTime 76800, CueTime 76805],
cueTrackPostgap = Nothing
},
CueTrack
{ cueTrackDigitalCopyPermitted = False,
cueTrackFourChannelAudio = False,
cueTrackPreemphasisEnabled = True,
cueTrackSerialCopyManagement = False,
cueTrackType = CueTrackAudio,
cueTrackIsrc = Nothing,
cueTrackTitle = Just track5Title,
cueTrackPerformer = Just performer,
cueTrackSongwriter = Nothing,
cueTrackPregap = Nothing,
cueTrackPregapIndex = Nothing,
cueTrackIndices = NE.fromList [CueTime 115800],
cueTrackPostgap = Nothing
},
CueTrack
{ cueTrackDigitalCopyPermitted = False,
cueTrackFourChannelAudio = False,
cueTrackPreemphasisEnabled = False,
cueTrackSerialCopyManagement = False,
cueTrackType = CueTrackAudio,
cueTrackIsrc = Nothing,
cueTrackTitle = Just track6Title,
cueTrackPerformer = Just performer,
cueTrackSongwriter = Nothing,
cueTrackPregap = Nothing,
cueTrackPregapIndex = Nothing,
cueTrackIndices = NE.fromList [CueTime 138750],
cueTrackPostgap = Nothing
},
CueTrack
{ cueTrackDigitalCopyPermitted = False,
cueTrackFourChannelAudio = False,
cueTrackPreemphasisEnabled = False,
cueTrackSerialCopyManagement = False,
cueTrackType = CueTrackAudio,
cueTrackIsrc = Nothing,
cueTrackTitle = Just track7Title,
cueTrackPerformer = Just performer,
cueTrackSongwriter = Nothing,
cueTrackPregap = Nothing,
cueTrackPregapIndex = Nothing,
cueTrackIndices = NE.fromList [CueTime 172800],
cueTrackPostgap = Nothing
},
CueTrack
{ cueTrackDigitalCopyPermitted = False,
cueTrackFourChannelAudio = False,
cueTrackPreemphasisEnabled = False,
cueTrackSerialCopyManagement = False,
cueTrackType = CueTrackAudio,
cueTrackIsrc = Nothing,
cueTrackTitle = Just track8Title,
cueTrackPerformer = Just performer,
cueTrackSongwriter = Nothing,
cueTrackPregap = Nothing,
cueTrackPregapIndex = Nothing,
cueTrackIndices = NE.fromList [CueTime 191625],
cueTrackPostgap = Nothing
}
]
}
]
}
-- | Construct 'CueParserFailure' from given @'ET' 'Word8'@ thing.
wrappedTrivial :: ET ByteString -> CueParserFailure
wrappedTrivial xs =
case err 0 xs of
TrivialError _ us es -> CueParserTrivialError us es
_ -> error "Ooops!"
-- | Quickly make a custom error component.
cstm :: e -> EF e
cstm = fancy . ErrorCustom
{-# INLINE cstm #-}