Add test-case

This commit is contained in:
Frederik Hanghøj Iversen 2019-10-18 22:20:26 +02:00
parent e6b3c8d8be
commit cb26e1727b
9 changed files with 108 additions and 12 deletions

View file

@ -34,6 +34,7 @@ dependencies:
- optparse-applicative - optparse-applicative
- temporary - temporary
- text - text
- hashable
default-extensions: default-extensions:
- ConstraintKinds - ConstraintKinds
@ -76,7 +77,7 @@ executables:
tests: tests:
test: test:
main: Spec.hs main: Main.hs
source-dirs: test source-dirs: test
ghc-options: ghc-options:
- -threaded - -threaded
@ -84,3 +85,7 @@ tests:
- -with-rtsopts=-N - -with-rtsopts=-N
dependencies: dependencies:
- rubyhs - rubyhs
- tasty
- tasty-hunit
- filepath
- directory

View file

@ -3,7 +3,7 @@
module Rubyhs (main) where module Rubyhs (main) where
import Data.Foldable (traverse_) import Data.Foldable (traverse_)
import Data.Map (Map) import Data.HashMap.Strict (HashMap)
import Data.Set (Set) import Data.Set (Set)
import Data.Tree (Tree(Node), Forest) import Data.Tree (Tree(Node), Forest)
import Frelude import Frelude
@ -31,7 +31,7 @@ main = do
Just dotPath -> drawDot dotPath x Just dotPath -> drawDot dotPath x
traverse_ act begins traverse_ act begins
drawDot :: FilePath -> Map Ruby.Node (Set Ruby.Node) -> IO () drawDot :: FilePath -> HashMap Ruby.Node (Set Ruby.Node) -> IO ()
drawDot p drawDot p
= Text.writeFile p = Text.writeFile p
. Graphviz.digraph . Graphviz.digraph

View file

@ -15,17 +15,21 @@ module Rubyhs.References
import Frelude import Frelude
-- import Data.Language.Ruby hiding (context) -- import Data.Language.Ruby hiding (context)
import qualified Data.Language.Ruby as Ruby import qualified Data.Language.Ruby as Ruby
import Data.Map (Map) import Data.HashMap.Strict (HashMap)
import qualified Data.Map as Map import qualified Data.HashMap.Strict as HashMap
import qualified Data.Aeson.Encoding.Internal as Aeson
import Data.Kind import Data.Kind
import Control.Monad.State import Control.Monad.State
import qualified Data.Aeson as Aeson import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import Data.Set (Set) import Data.Set (Set)
import qualified Data.Set as Set import qualified Data.Set as Set
import qualified Data.Text as Text import qualified Data.Text as Text
import Data.Coerce import Data.Coerce
import Data.Graph (Graph, Vertex, Forest) import Data.Graph (Graph, Vertex, Forest)
import qualified Data.Graph as Graph import qualified Data.Graph as Graph
import Data.Hashable
import qualified Data.Char as Char
span :: G node key -> Forest node span :: G node key -> Forest node
span (g, f, _) = fmap ((\(x, _, _) -> x) . f) <$> Graph.dff g span (g, f, _) = fmap ((\(x, _, _) -> x) . f) <$> Graph.dff g
@ -49,6 +53,8 @@ instance IsList Namespace where
toList = coerce toList = coerce
deriving newtype instance Semigroup Namespace deriving newtype instance Semigroup Namespace
deriving newtype instance Monoid Namespace deriving newtype instance Monoid Namespace
instance FromJSON Namespace where
parseJSON = Aeson.withText "Namespace" $ \t -> Namespace <$> (traverse pure $ Text.splitOn "::" t)
instance ToJSON Namespace where instance ToJSON Namespace where
toJSON = Aeson.String . showNamespace toJSON = Aeson.String . showNamespace
@ -63,14 +69,32 @@ data FQN = FQN
data Node = NodeDef FQN | NodeModule Namespace data Node = NodeDef FQN | NodeModule Namespace
beginsWithCapital :: Text -> Bool
beginsWithCapital = Char.isUpper . Text.head
deriving stock instance Show Node deriving stock instance Show Node
deriving stock instance Eq Node deriving stock instance Eq Node
deriving stock instance Ord Node deriving stock instance Ord Node
instance FromJSON Node where
parseJSON = Aeson.withText "Node" $ nodeParser
nodeParser :: Text -> Aeson.Parser Node
nodeParser = \t -> case Text.splitOn "." t of
[mods,fun] -> fmap NodeDef (FQN <$> Aeson.parseJSON (Aeson.String mods) <*> pure fun)
[mods] ->
if beginsWithCapital mods
then NodeModule <$> Aeson.parseJSON (Aeson.String mods)
else pure $ NodeDef $ FQN mempty mods
_ -> empty
instance Aeson.FromJSONKey Node where
fromJSONKey = Aeson.FromJSONKeyTextParser nodeParser
instance Aeson.ToJSONKey Node where
toJSONKey = Aeson.ToJSONKeyText prettyContext (\n -> Aeson.text $ prettyContext n)
instance ToJSON Node where instance ToJSON Node where
toJSON = \case toJSON = \case
NodeDef q -> Aeson.toJSON q NodeDef q -> Aeson.toJSON q
NodeModule m -> Aeson.toJSON m NodeModule m -> Aeson.toJSON m
instance Hashable Node where
hashWithSalt n a = hashWithSalt @Text n $ prettyContext a
deriving stock instance Show FQN deriving stock instance Show FQN
deriving stock instance Eq FQN deriving stock instance Eq FQN
@ -110,11 +134,13 @@ class Monad m => MyMonad (m :: Type -> Type) where
data Env = Env data Env = Env
{ declarations :: Set Node { declarations :: Set Node
, applications :: Map Node (Set Node) , applications :: HashMap Node (Set Node)
, context :: Context , context :: Context
} }
newtype Result = Result (Map Node (Set Node)) newtype Result = Result (HashMap Node (Set Node))
-- HACK: FromJSON and ToJSON don't agree.
deriving newtype instance FromJSON Result
instance ToJSON Result where instance ToJSON Result where
toJSON (Result a) = Aeson.Object $ fromList $ go <$> toList a toJSON (Result a) = Aeson.Object $ fromList $ go <$> toList a
where where
@ -135,7 +161,7 @@ instance MyMonad (State Env) where
Context c <- getContext Context c <- getContext
let let
go env@Env{applications} go env@Env{applications}
= env { applications = Map.insertWith mappend c (Set.singleton n) applications } = env { applications = HashMap.insertWith mappend c (Set.singleton n) applications }
modify go modify go
getContext = gets Rubyhs.References.context getContext = gets Rubyhs.References.context
writeContext q = modify go writeContext q = modify go
@ -153,7 +179,7 @@ class References a where
entries :: MyMonad m => a -> m () entries :: MyMonad m => a -> m ()
references :: Ruby.Begin -> Result references :: Ruby.Begin -> Result
references q = Result $ Map.unionWith mappend applications declarations' references q = Result $ HashMap.unionWith mappend applications declarations'
where where
Env{declarations,applications} = execState (entries @_ @(State Env) q) emptyEnv Env{declarations,applications} = execState (entries @_ @(State Env) q) emptyEnv
emptyEnv = Env mempty mempty (Context $ NodeModule mempty) emptyEnv = Env mempty mempty (Context $ NodeModule mempty)

40
test/Main.hs Normal file
View file

@ -0,0 +1,40 @@
{-# LANGUAGE BangPatterns #-}
module Main (main) where
import Data.List
import Data.Ord
import Frelude
import Test.Tasty
import Test.Tasty.HUnit
import qualified Data.Language.Ruby as Ruby
import qualified Rubyhs.References as Ruby
import qualified Data.Aeson as Aeson
import System.FilePath
import Data.Map (Map)
import Data.HashMap.Strict (HashMap)
import Data.Set (Set)
import System.Directory (withCurrentDirectory)
import Debug.Trace
import System.IO
import qualified Data.ByteString.Lazy.Char8 as ByteString
main :: IO ()
main = withCurrentDirectory "test" $ defaultMain $ testGroup "Unit tests" tests
tests :: [TestTree]
tests = go <$> ["mod", "simple"]
where
go :: String -> TestTree
go s = testCase s $ do
let basename = "tests" </> s
begin <- Ruby.parseFile @Ruby.Begin $ basename <.> "rb"
json <- decodeFile @(HashMap Ruby.Node (Set Ruby.Node)) $ basename <.> "json"
let (Ruby.Result refs) = Ruby.references begin
when (refs /= json)
$ assertFailure
$ "Expected " <> convertString (Aeson.encode refs) <> " but got " <> convertString (Aeson.encode json)
decodeFile :: FromJSON a => FilePath -> IO a
decodeFile p = Aeson.eitherDecodeFileStrict' p >>= \case
Left err -> error err
Right a -> pure a

View file

@ -1,2 +0,0 @@
main :: IO ()
main = putStrLn "Test suite not yet implemented"

7
test/tests/mod.json Normal file
View file

@ -0,0 +1,7 @@
{
"A.b": [
"A.a"
],
"A.a": [],
"A": []
}

8
test/tests/mod.rb Normal file
View file

@ -0,0 +1,8 @@
module A
def a
end
def b
a
end
end

6
test/tests/simple.json Normal file
View file

@ -0,0 +1,6 @@
{
"a": [],
"b": [
"a"
]
}

6
test/tests/simple.rb Normal file
View file

@ -0,0 +1,6 @@
def a
end
def b
a
end