forked from digital-asset/daml
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a damlc doctest command and test the standard library (digital-as…
…set#2157) There is lots of room for improvements here but I think this is a good first step. The 3 main things that could be improved imho are: - Rewrite source locations to point to the original file rather than the generated module - Provide some way to declare things like imports or more general, setup code that is added to the generated module. - Prettier/more helpful output during a run, e.g., print the list of successful tests.
- Loading branch information
1 parent
0fc0ca6
commit 5aa3cba
Showing
17 changed files
with
538 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
compiler/damlc/daml-compiler/src/DA/Daml/Compiler/DocTest.hs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. | ||
-- SPDX-License-Identifier: Apache-2.0 | ||
|
||
{-# LANGUAGE OverloadedStrings #-} | ||
module DA.Daml.Compiler.DocTest (docTest) where | ||
|
||
import Control.Monad | ||
import DA.Daml.Options.Types | ||
import DA.Daml.DocTest | ||
import qualified Data.Set as Set | ||
import qualified Data.Text.Extended as T | ||
import Development.IDE.Core.API | ||
import Development.IDE.Core.RuleTypes.Daml | ||
import Development.IDE.Core.Shake | ||
import Development.IDE.Types.Diagnostics | ||
import Development.IDE.Types.Location | ||
import qualified Development.Shake as Shake | ||
import System.Directory | ||
import System.Exit | ||
import System.FilePath | ||
|
||
-- We might want merge the logic for `damlc doctest` and `damlc test` | ||
-- at some point but the requirements are slightly different (e.g., we | ||
-- will eventually want to remap source locations in `damlc doctest` | ||
-- to the doc comment) so for now we keep them separate. | ||
|
||
docTest :: IdeState -> [NormalizedFilePath] -> IO () | ||
docTest ideState files = do | ||
ms <- runAction ideState (uses_ GenerateDocTestModule files) | ||
let docTestFile m = toNormalizedFilePath $ | ||
genDir </> | ||
T.unpack (T.replace "." "/" (docTestModuleName $ genModuleName m)) -<.> | ||
"daml" | ||
let msWithPaths = map (\m -> (m, docTestFile m)) ms | ||
forM_ msWithPaths $ \(m, path) -> do | ||
createDirectoryIfMissing True (takeDirectory $ fromNormalizedFilePath path) | ||
T.writeFileUtf8 (fromNormalizedFilePath path) (genModuleContent m) | ||
setFilesOfInterest ideState (Set.fromList $ map snd msWithPaths) | ||
runAction ideState $ do | ||
void $ Shake.forP msWithPaths $ \(_, path) -> use_ RunScenarios path | ||
-- This seems to make the gRPC issues on shutdown slightly less | ||
-- frequent but sadly it doesn’t make them go away completely. | ||
runActionSync ideState (pure ()) | ||
diags <- getDiagnostics ideState | ||
when (any ((Just DsError ==) . _severity . snd) diags) exitFailure | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
load("//bazel_tools:haskell.bzl", "da_haskell_library") | ||
|
||
da_haskell_library( | ||
name = "daml-doctest", | ||
srcs = glob(["src/**/*.hs"]), | ||
hazel_deps = [ | ||
"base", | ||
"deepseq", | ||
"ghc-lib", | ||
"ghc-lib-parser", | ||
"syb", | ||
"text", | ||
], | ||
src_strip_prefix = "src", | ||
visibility = ["//visibility:public"], | ||
deps = [ | ||
"//compiler/hie-core", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. | ||
-- SPDX-License-Identifier: Apache-2.0 | ||
|
||
{-# LANGUAGE OverloadedStrings #-} | ||
module DA.Daml.DocTest | ||
( getDocTestModule | ||
, docTestModuleName | ||
, GeneratedModule(..) | ||
) where | ||
|
||
import Control.DeepSeq | ||
import Data.Char | ||
import Data.Generics hiding (Generic) | ||
import Data.List | ||
import Data.Text (Text) | ||
import qualified Data.Text as T | ||
import GHC hiding (parseModule) | ||
import GHC.Generics (Generic) | ||
|
||
-- Most of this code is a variation of the code in Haskell’s doctest, in particular | ||
-- the [Extract](https://github.com/sol/doctest/blob/master/src/Extract.hs) module for | ||
-- extracting doc comments from a `ParsedModule` and the | ||
-- [Parse](https://github.com/sol/doctest/blob/master/src/Parse.hs) module for | ||
-- extracting tests from doc comments. | ||
-- Our requirements are somewhat different (e.g. we don’t use actual GHCi commands) | ||
-- so we roll our own version. | ||
|
||
---------------------------------------------- | ||
-- Types shared across the different stages -- | ||
---------------------------------------------- | ||
|
||
data DocTestModule a = DocTestModule | ||
{ dtModuleName :: Text | ||
, dtModuleContent :: [a] | ||
-- For now, we do not handle setup but we probably will | ||
-- have to handle it at some point | ||
} deriving (Eq, Show, Functor) | ||
|
||
-------------------------------- | ||
-- Extraction of doc comments -- | ||
-------------------------------- | ||
|
||
extractFromModule :: ParsedModule -> DocTestModule LHsDocString | ||
extractFromModule m = DocTestModule name (map snd docs) | ||
where | ||
name = T.pack . moduleNameString . GHC.moduleName . ms_mod . pm_mod_summary $ m | ||
docs = docStringsFromModule m | ||
|
||
-- | We return a tuple of the doc comment name (if any) and the actual doc. | ||
-- The name allows us to differentiate between setup and regular doc comments. | ||
docStringsFromModule :: ParsedModule -> [(Maybe String, LHsDocString)] | ||
docStringsFromModule mod = docs | ||
where | ||
-- TODO We might want to mine docs from exports and the header as well. | ||
docs = decls | ||
source = unLoc . pm_parsed_source $ mod | ||
decls = extractDocStrings $ hsmodDecls source | ||
|
||
-- | Extract all docstrings from given value. | ||
extractDocStrings :: Data a => a -> [(Maybe String, LHsDocString)] | ||
extractDocStrings = everythingBut (++) (([], False) `mkQ` fromLHsDecl | ||
`extQ` fromLDocDecl | ||
`extQ` fromLHsDocString | ||
) | ||
where | ||
fromLHsDecl :: Selector (LHsDecl GhcPs) | ||
fromLHsDecl (L loc decl) = case decl of | ||
-- Top-level documentation has to be treated separately, because it has | ||
-- no location information attached. The location information is | ||
-- attached to HsDecl instead. | ||
DocD _ x -> select (fromDocDecl loc x) | ||
_ -> (extractDocStrings decl, True) | ||
|
||
fromLDocDecl :: Selector LDocDecl | ||
fromLDocDecl (L loc x) = select (fromDocDecl loc x) | ||
|
||
fromLHsDocString :: Selector LHsDocString | ||
fromLHsDocString x = select (Nothing, x) | ||
|
||
fromDocDecl :: SrcSpan -> DocDecl -> (Maybe String, LHsDocString) | ||
fromDocDecl loc x = case x of | ||
DocCommentNamed name doc -> (Just name, L loc doc) | ||
_ -> (Nothing, L loc $ docDeclDoc x) | ||
|
||
type Selector a = a -> ([(Maybe String, LHsDocString)], Bool) | ||
select :: a -> ([a], Bool) | ||
select x = ([x], False) | ||
|
||
--------------------------------------- | ||
-- Parse doc comments into doc tests -- | ||
--------------------------------------- | ||
|
||
data DocTest = DocTest | ||
{ dtExpr :: Text | ||
, dtExpectedResult :: [Text] | ||
} | ||
|
||
parseModule :: DocTestModule LHsDocString -> DocTestModule (Located DocTest) | ||
parseModule m = m { dtModuleContent = concatMap parseDocTests $ dtModuleContent m } | ||
|
||
parseDocTests :: LHsDocString -> [Located DocTest] | ||
parseDocTests docString = | ||
map (L (getLoc docString)) . go . T.lines . T.pack . unpackHDS . unLoc $ docString | ||
where | ||
isPrompt :: Text -> Bool | ||
isPrompt = T.isPrefixOf ">>>" . T.dropWhile isSpace | ||
dropPromptPrefix = T.drop 3 . T.dropWhile isSpace | ||
|
||
isBlankLine :: Text -> Bool | ||
isBlankLine = T.all isSpace | ||
|
||
isCodeBlockEnd :: Text -> Bool | ||
isCodeBlockEnd = T.isInfixOf "```" | ||
|
||
isEndOfInteraction :: Text -> Bool | ||
isEndOfInteraction x = isPrompt x || isBlankLine x || isCodeBlockEnd x | ||
|
||
go :: [Text] -> [DocTest] | ||
go xs = case dropWhile (not . isPrompt) xs of | ||
prompt:rest -> | ||
let (ys,zs) = break isEndOfInteraction rest | ||
in toDocTest (T.strip $ dropPromptPrefix prompt) ys : go zs | ||
[] -> [] | ||
|
||
toDocTest :: Text -> [Text] -> DocTest | ||
toDocTest = DocTest | ||
|
||
--------------------------- | ||
-- Render doctest module -- | ||
--------------------------- | ||
|
||
-- | Identifier for a doctest. This allows us to map | ||
-- the doctest back to the source in the original module. | ||
newtype DocTestId = DocTestId Int | ||
deriving (Eq, Ord, Show, Enum) | ||
|
||
renderDocTestModule :: DocTestModule (Located DocTest) -> Text | ||
renderDocTestModule DocTestModule{..} = rendered | ||
where | ||
testsWithIds = zip [DocTestId 0..] dtModuleContent | ||
rendered = T.unlines $ | ||
[ "{-# OPTIONS_GHC -Wno-unused-imports #-}" | ||
, "daml 1.2" | ||
, "module " <> docTestModuleName dtModuleName <> " where" | ||
, "" | ||
, "import " <> dtModuleName | ||
, "import DA.Assert" | ||
, "" | ||
] <> | ||
intercalate [""] (map (uncurry renderDocTest) testsWithIds) | ||
|
||
renderDocTest :: DocTestId -> Located DocTest -> [Text] | ||
renderDocTest (DocTestId i) (unLoc -> DocTest{..}) = | ||
[ "doctest_" <> T.pack (show i) <> " = " <> "scenario do" | ||
, " (===) (" <> dtExpr <> ") $" | ||
] <> | ||
map (indent 4) dtExpectedResult | ||
where | ||
indent i t = T.replicate i " " <> t | ||
|
||
getDocTestModule :: ParsedModule -> GeneratedModule | ||
getDocTestModule pm = case parseModule $ extractFromModule pm of | ||
m -> GeneratedModule (dtModuleName m) (renderDocTestModule m) | ||
|
||
docTestModuleName :: Text -> Text | ||
docTestModuleName t = t <> "_doctest" | ||
|
||
data GeneratedModule = GeneratedModule | ||
{ genModuleName :: Text | ||
, genModuleContent :: Text | ||
} deriving (Show, Generic) | ||
|
||
instance NFData GeneratedModule | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.