packages feed

dojang-0.1.0: test/Dojang/Syntax/EnvironmentPredicate/WriterSpec.hs

{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}

module Dojang.Syntax.EnvironmentPredicate.WriterSpec (spec) where

import Data.Text (Text, unpack)
import Test.Hspec (Spec, describe, it, specify)
import Test.Hspec.Expectations.Pretty (shouldBe)
import Test.Hspec.Hedgehog (annotateShow, footnote, forAll, hedgehog, (===))

import Dojang.Syntax.EnvironmentPredicate.Parser
  ( errorBundlePretty
  , parseEnvironmentPredicate
  )
import Dojang.Syntax.EnvironmentPredicate.Writer (writeEnvironmentPredicate)
import Dojang.Types.EnvironmentPredicate
  ( EnvironmentPredicate (..)
  , normalizePredicate
  )
import Dojang.Types.Gen qualified as Gen
import Dojang.Types.MonikerName (parseMonikerName)


write :: EnvironmentPredicate -> Text
write = writeEnvironmentPredicate


spec :: Spec
spec = do
  describe "writeEnvironmentPredicate" $ do
    specify "simple expressions" $ do
      write Always `shouldBe` "always"
      write (Not Always) `shouldBe` "never"
      write (OperatingSystem "linux") `shouldBe` "os = linux"
      write (Architecture "x86_64")
        `shouldBe` "arch = \"x86_64\""
      let Right fooBar = parseMonikerName "foo-bar"
      write (Moniker fooBar) `shouldBe` "moniker = \"foo-bar\""

    it "escape special characters in string literal" $ do
      write (OperatingSystem "\\'\"\b\f\n\r\t\v\0\x01\xff\xffff")
        `shouldBe` "os = \"\\\\'\\\"\\b\\f\\n\\r\\t\\v\\0\\x01\\xff\\uffff\""
      write (OperatingSystem "\\'\b\f\n\r\t\v\0\x01\xff\xffff")
        `shouldBe` "os = \"\\\\'\\b\\f\\n\\r\\t\\v\\0\\x01\\xff\\uffff\""

    specify "string literal is around by quotes if needed" $ do
      write (OperatingSystem "test") `shouldBe` "os = test"
      write (OperatingSystem "te st") `shouldBe` "os = \"te st\""
      write (OperatingSystem "") `shouldBe` "os = \"\""

    specify "string literal is around by single quotes if it has double quote"
      $ do
        write (OperatingSystem "\"test\"") `shouldBe` "os = '\"test\"'"
        write (OperatingSystem "\"test'") `shouldBe` "os = \"\\\"test'\""

    specify "homogenous predicates in Or are expressed using in operator" $ do
      write (Or [OperatingSystem "linux", OperatingSystem "macos"])
        `shouldBe` "os in (linux, macos)"
      write (Or [Architecture "x86_64", Architecture "aarch64"])
        `shouldBe` "arch in (\"x86_64\", aarch64)"
      let Right foo = parseMonikerName "foo"
      let Right bar = parseMonikerName "bar"
      write (Or [Moniker foo, Moniker bar]) `shouldBe` "moniker in (foo, bar)"

    specify
      ( "homogenous negative predicates in And are expressed using "
          ++ "not in operator"
      )
      $ do
        write
          (And [Not $ OperatingSystem "linux", Not $ OperatingSystem "macos"])
          `shouldBe` "os not in (linux, macos)"
        write (And [Not $ Architecture "x86_64", Not $ Architecture "aarch64"])
          `shouldBe` "arch not in (\"x86_64\", aarch64)"
        let Right foo = parseMonikerName "foo"
        let Right bar = parseMonikerName "bar"
        write (And [Not $ Moniker foo, Not $ Moniker bar])
          `shouldBe` "moniker not in (foo, bar)"

    it "properly wrap sub-expressions with parentheses" $ do
      let Right foo = parseMonikerName "foo"
      let Right bar = parseMonikerName "bar"
      write
        ( And
            [ Or [OperatingSystem "linux", Architecture "aarch64"]
            , Or [Moniker foo, Moniker bar]
            ]
        )
        `shouldBe` "(os = linux || arch = aarch64) && moniker in (foo, bar)"

    specify "yields what parseEnvironmentPredicate can parse" $ hedgehog $ do
      predicate <- normalizePredicate <$> forAll Gen.environmentPredicate
      let predText = write predicate
      annotateShow predText
      let parseResult = parseEnvironmentPredicate "test" predText
      case parseResult of
        Left err -> footnote $ unpack $ errorBundlePretty err
        Right _ -> return ()
      let Right pred' = parseResult
      pred' === predicate