packages feed

sensei-0.9.0: test/ReadHandleSpec.hs

module ReadHandleSpec (spec) where

import           Prelude hiding (span)
import           Helper

import           Test.QuickCheck
import qualified Data.ByteString as ByteString

import           Session (Summary(..), extractSummary)
import           Language.Haskell.GhciWrapper (extractReloadDiagnostics, extractDiagnostics)

import           ReadHandle

chunkByteString :: (Int, Int) -> ByteString -> Gen [ByteString]
chunkByteString size = go
  where
    go "" = return []
    go xs = do
      n <- chooseInt size
      let (chunk, rest) = ByteString.splitAt n xs
      (chunk :) <$> go rest

fakeHandle :: [ByteString] -> IO ReadHandle
fakeHandle chunks = ReadHandle <$> stubAction chunks <*> newEmptyBuffer

data ChunkSizes = SmallChunks | BigChunks

withRandomChunkSizes :: [ByteString] -> (ReadHandle -> Expectation) -> Property
withRandomChunkSizes (mconcat -> input) action = property $ do
  chunkSizes <- elements [SmallChunks, BigChunks]
  let
    maxChunkSize :: Int
    maxChunkSize = case chunkSizes of
      SmallChunks -> 4
      BigChunks -> ByteString.length input

  chunks <- chunkByteString (1, maxChunkSize) input
  return $ do
    counterexample (unlines $ map show chunks) $ do
      fakeHandle chunks >>= action

partialMarker :: ByteString
partialMarker = ByteString.take 5 marker

spec :: Spec
spec = do
  describe "breakAfterNewLine" $ do
    it "brakes after newline" $ do
      breakAfterNewLine "foo\nbar\nbaz" `shouldBe` Just ("foo\n", "bar\nbaz")

  describe "drain" $ do
    it "drains all remaining input" $ do
      h <- fakeHandle ["foo", marker, "bar", marker, "baz", marker, ""]
      withSpy (drain extractReloadDiagnostics h) `shouldReturn` ["foo", "bar", "baz"]

  describe "getResult" $ do
    context "with a single result" $ do
      let input = ["foo", "bar", "baz", marker]

      it "returns result" $ do
        withSpy $ \ echo -> do
          h <- fakeHandle input
          getResult extractReloadDiagnostics h echo `shouldReturn` ("foobarbaz", [])
        `shouldReturn` ["foo", "bar", "baz"]

      context "with chunks of arbitrary size" $ do
        it "returns result" $ do
          withRandomChunkSizes input $ \ h -> do
            fmap mconcat . withSpy $ \ echo -> do
              getResult extractReloadDiagnostics h echo `shouldReturn` ("foobarbaz", [])
            `shouldReturn` "foobarbaz"

      context "with extractSummary" $ do
        let
          summary :: Summary
          summary = Summary 5 3

        it "extracts Summary" $ do
          let
            chunks :: [ByteString]
            chunks = [
                "foo\n"
              , "bar\n"
              , encodeUtf8 $ show summary <> "\n"
              , "baz\n"
              , marker
              ]

          withRandomChunkSizes chunks $ \ h -> do
            fmap mconcat . withSpy $ \ echo -> do
              getResult extractSummary h echo `shouldReturn` ("foo\nbar\nbaz\n", [summary])
            `shouldReturn` "foo\nbar\nbaz\n"

        context "when predicate does not match" $ do
          it "does not attempt parsing" $ do
            let
              extract :: Extract Summary
              extract = extractSummary {parseMessage = undefined}

              chunks :: [ByteString]
              chunks = [
                  "foo\n"
                , "bar\n"
                , encodeUtf8 . take 8 $ show summary <> "\n"
                , "baz\n"
                , marker
                ]

            withRandomChunkSizes chunks $ \ h -> do
              fmap mconcat . withSpy $ \ echo -> do
                getResult extract h echo `shouldReturn` ("foo\nbar\nSummary baz\n", [])
              `shouldReturn` "foo\nbar\nSummary baz\n"

      context "with extractDiagnostics" $ do
        let
          extract :: Extract Diagnostic
          extract = extractDiagnostics {
            parseMessage = fmap (second $ const "") . extractDiagnostics.parseMessage
          }

        it "extracts Diagnostic" $ do
          let
            start :: Location
            start = Location 23 42

            span :: Maybe Span
            span = Just $ Span "Foo.hs" start start

            err1 :: Diagnostic
            err1 = (diagnostic Error) { span }

            err2 :: Diagnostic
            err2 = err1 { code = Just 23 }

            chunks :: [ByteString]
            chunks = [
                "foo\n"
              , "bar\n"
              , to_json err1 <> "\n"
              , "baz\n"
              , to_json err2 { code = Just 23 } <> "\n"
              , marker
              ]
          withRandomChunkSizes chunks $ \ h -> do
            fmap mconcat . withSpy $ \ echo -> do
              getResult extract h echo `shouldReturn` ("foo\nbar\nbaz\n", [err1, err2])
            `shouldReturn` "foo\nbar\nbaz\n"

        context "with a partial match" $ do
          it "retains the original input" $ do
            let
              chunks :: [ByteString]
              chunks = [
                  "foo\n"
                , "bar\n"
                , "{..."
                , marker
                ]
            withRandomChunkSizes chunks $ \ h -> do
              fmap mconcat . withSpy $ \ echo -> do
                getResult extract h echo `shouldReturn` ("foo\nbar\n{...", [])
              `shouldReturn` "foo\nbar\n{..."

    context "with multiple results" $ do
      let input = ["foo", marker, "bar", marker, "baz", marker]

      it "returns one result at a time" $ do
        withSpy $ \ echo -> do
          h <- fakeHandle input
          getResult extractReloadDiagnostics h echo `shouldReturn` ("foo", [])
          getResult extractReloadDiagnostics h echo `shouldReturn` ("bar", [])
          getResult extractReloadDiagnostics h echo `shouldReturn` ("baz", [])
        `shouldReturn` ["foo", "bar", "baz"]

      context "with chunks of arbitrary size" $ do
        it "returns one result at a time" $ do
          withRandomChunkSizes input $ \ h -> do
            fmap mconcat . withSpy $ \ echo -> do
              getResult extractReloadDiagnostics h echo `shouldReturn` ("foo", [])
              getResult extractReloadDiagnostics h echo `shouldReturn` ("bar", [])
              getResult extractReloadDiagnostics h echo `shouldReturn` ("baz", [])
            `shouldReturn` "foobarbaz"

    context "when a chunk that contains a marker ends with a partial marker" $ do
      it "correctly gives the marker precedence over the partial marker" $ do
        withSpy $ \ echo -> do
          h <- fakeHandle ["foo" <> marker <> "bar" <> partialMarker, ""]
          getResult extractReloadDiagnostics h echo `shouldReturn` ("foo", [])
          getResult extractReloadDiagnostics h echo `shouldReturn` ("bar" <> partialMarker, [])
        `shouldReturn` ["foo", "bar", partialMarker]

    context "on EOF" $ do
      it "returns all remaining input" $ do
        withSpy $ \ echo -> do
          h <- fakeHandle ["foo", "bar", "baz", ""]
          getResult extractReloadDiagnostics h echo `shouldReturn` ("foobarbaz", [])
        `shouldReturn` ["foo", "bar", "baz"]

      context "with a partialMarker at the end" $ do
        it "includes the partial marker in the output" $ do
          withSpy $ \ echo -> do
            h <- fakeHandle ["foo", "bar", "baz", partialMarker, ""]
            getResult extractReloadDiagnostics h echo `shouldReturn` ("foobarbaz" <> partialMarker, [])
          `shouldReturn` ["foo", "bar", "baz", partialMarker]

      context "after a marker" $ do
        it "returns all remaining input" $ do
          withSpy $ \ echo -> do
            h <- fakeHandle ["foo", "bar", "baz", marker, "qux", ""]
            getResult extractReloadDiagnostics h echo `shouldReturn` ("foobarbaz", [])
            getResult extractReloadDiagnostics h echo `shouldReturn` ("qux", [])
          `shouldReturn` ["foo", "bar", "baz", "qux"]