|
| 1 | +module LexerSpec (tests) where |
| 2 | + |
| 3 | +import Test.Tasty |
| 4 | +import Test.Tasty.HUnit |
| 5 | + |
| 6 | +import qualified Data.ByteString.Char8 as P |
| 7 | + |
| 8 | +import Lexer (doP, doF, doS, doI) |
| 9 | +import Syntax |
| 10 | + |
| 11 | +tests :: TestTree |
| 12 | +tests = testGroup "Lexer" |
| 13 | + [ testGroup "doP (status messages)" |
| 14 | + [ testCase "0 is Stopped" $ status (doP "0") @?= Stopped |
| 15 | + , testCase "1 is Paused" $ status (doP "1") @?= Paused |
| 16 | + , testCase "2 is Playing" $ status (doP "2") @?= Playing |
| 17 | + , testCase "3 is Playing" $ status (doP "3") @?= Playing |
| 18 | + , testCase "empty is Playing" $ status (doP "") @?= Playing |
| 19 | + , testCase "garbage is Playing" $ status (doP "xyz") @?= Playing |
| 20 | + ] |
| 21 | + , testGroup "doF (frame messages)" |
| 22 | + [ testCase "all four fields parse" $ do |
| 23 | + let Frame{..} = frame (doF "123 456 12.34 56.78") |
| 24 | + currentFrame @?= 123 |
| 25 | + framesLeft @?= 456 |
| 26 | + currentTime @?= 12.34 |
| 27 | + timeLeft @?= 56.78 |
| 28 | + , testCase "negative timeLeft is clamped to zero" $ do |
| 29 | + timeLeft (frame (doF "0 0 0.00 -1.00")) @?= 0 |
| 30 | + ] |
| 31 | + , testGroup "doS (info messages)" |
| 32 | + [ testCase "userinfo combines version, bitrate, kHz" $ |
| 33 | + userinfo (info (doS "1.0 1 44100 stereo 0 0 2 0 0 0 128 0")) |
| 34 | + @?= "mpeg 1.0 128kbit/s 44kHz" |
| 35 | + ] |
| 36 | + , testGroup "doI (track info / id3)" |
| 37 | + [ testCase "non-id3 input becomes Left filename" $ |
| 38 | + fileLeft (doI "song.mp3") @?= "song.mp3" |
| 39 | + , testCase "non-id3 input is trimmed" $ |
| 40 | + fileLeft (doI " song.mp3 ") @?= "song.mp3" |
| 41 | + , testCase "id3 with title only" $ do |
| 42 | + let i = fileRight (doI ("ID3:" <> field30 "Title")) |
| 43 | + id3title i @?= "Title" |
| 44 | + id3artist i @?= "" |
| 45 | + id3album i @?= "" |
| 46 | + id3str i @?= "Title" |
| 47 | + , testCase "id3 with title and artist" $ do |
| 48 | + let i = fileRight (doI ("ID3:" <> field30 "Title" <> field30 "Artist")) |
| 49 | + id3title i @?= "Title" |
| 50 | + id3artist i @?= "Artist" |
| 51 | + id3album i @?= "" |
| 52 | + id3str i @?= "Artist : Title" |
| 53 | + , testCase "id3 with title, artist, and album" $ do |
| 54 | + let i = fileRight (doI ("ID3:" <> field30 "Title" |
| 55 | + <> field30 "Artist" |
| 56 | + <> field30 "Album")) |
| 57 | + id3title i @?= "Title" |
| 58 | + id3artist i @?= "Artist" |
| 59 | + id3album i @?= "Album" |
| 60 | + id3str i @?= "Artist : Album : Title" |
| 61 | + , testCase "id3 with empty title falls back to Left of trimmed input" $ |
| 62 | + -- mpg123 sometimes returns ID3 records with a blank title; rather than |
| 63 | + -- present an empty track name, the parser exposes the raw line. |
| 64 | + fileLeft (doI ("ID3:" <> field30 "" <> field30 "Artist")) |
| 65 | + @?= "ID3:" <> P.replicate 30 ' ' <> "Artist" |
| 66 | + ] |
| 67 | + ] |
| 68 | + |
| 69 | +------------------------------------------------------------------------ |
| 70 | +-- Helpers |
| 71 | + |
| 72 | +-- Pad/truncate a ByteString to exactly 30 characters with trailing spaces, |
| 73 | +-- matching the fixed-width field convention used by mpg123 ID3 output. |
| 74 | +field30 :: P.ByteString -> P.ByteString |
| 75 | +field30 b = P.take 30 (b <> P.replicate 30 ' ') |
| 76 | + |
| 77 | +status :: Msg -> Status |
| 78 | +status (S s) = s |
| 79 | +status _ = error "expected S" |
| 80 | + |
| 81 | +frame :: Msg -> Frame |
| 82 | +frame (R f) = f |
| 83 | +frame _ = error "expected R" |
| 84 | + |
| 85 | +info :: Msg -> Info |
| 86 | +info (I i) = i |
| 87 | +info _ = error "expected I" |
| 88 | + |
| 89 | +fileLeft :: Msg -> P.ByteString |
| 90 | +fileLeft (F (File (Left s))) = s |
| 91 | +fileLeft _ = error "expected F (File (Left _))" |
| 92 | + |
| 93 | +fileRight :: Msg -> Id3 |
| 94 | +fileRight (F (File (Right i))) = i |
| 95 | +fileRight _ = error "expected F (File (Right _))" |
0 commit comments