{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module NixLanguageTests (genTests) where import Control.Arrow ( (&&&) ) import Control.Exception import Control.Monad import Control.Monad.IO.Class import Control.Monad.ST import Data.List ( delete , sort ) import Data.List.Split ( splitOn ) import Data.Map ( Map ) import qualified Data.Map as Map import Data.Set ( Set ) import qualified Data.Set as Set import qualified Data.Text as Text import qualified Data.Text.IO as Text import Data.Time import GHC.Exts import Nix.Lint import Nix.Options import Nix.Options.Parser import Nix.Parser import Nix.Pretty import Nix.String import Nix.Utils import Nix.XML import qualified Options.Applicative as Opts import System.Environment import System.FilePath import System.FilePath.Glob ( compile , globDir1 ) import Test.Tasty import Test.Tasty.HUnit import TestCommon {- From (git://nix)/tests/lang.sh we see that lang/parse-fail-*.nix -> parsing should fail lang/parse-okay-*.nix -> parsing should succeed lang/eval-fail-*.nix -> eval should fail lang/eval-okay-*.{nix,xml} -> eval should succeed, xml dump should be the same as the .xml lang/eval-okay-*.{nix,exp} -> eval should succeed, plain text output should be the same as the .exp lang/eval-okay-*.{nix,exp,flags} -> eval should succeed, plain text output should be the same as the .exp, pass the extra flags to nix-instantiate NIX_PATH=lang/dir3:lang/dir4 should be in the environment of all eval-okay-*.nix evaluations TEST_VAR=foo should be in all the environments # for eval-okay-getenv.nix -} groupBy :: Ord k => (v -> k) -> [v] -> Map k [v] groupBy key = Map.fromListWith (++) . map (key &&& pure) -- | New tests, which have never yet passed. Once any of these is passing, -- please remove it from this list. Do not add tests to this list if they have -- previously passed. newFailingTests :: Set String newFailingTests = Set.fromList [ "eval-okay-path" , "eval-okay-fromTOML" , "eval-okay-context-introspection" ] genTests :: IO TestTree genTests = do testFiles <- sort -- jww (2018-05-07): Temporarily disable this test until #128 is fixed. . filter ((`Set.notMember` newFailingTests) . takeBaseName) . filter ((/= ".xml") . takeExtension) <$> globDir1 (compile "*-*-*.*") "data/nix/tests/lang" let testsByName = groupBy (takeFileName . dropExtensions) testFiles let testsByType = groupBy testType (Map.toList testsByName) let testGroups = map mkTestGroup (Map.toList testsByType) return $ localOption (mkTimeout 2000000) $ testGroup "Nix (upstream) language tests" testGroups where testType (fullpath, _files) = take 2 $ splitOn "-" $ takeFileName fullpath mkTestGroup (kind, tests) = testGroup (unwords kind) $ map (mkTestCase kind) tests mkTestCase kind (basename, files) = testCase (takeFileName basename) $ do time <- liftIO getCurrentTime let opts = defaultOptions time case kind of ["parse", "okay"] -> assertParse opts $ the files ["parse", "fail"] -> assertParseFail opts $ the files ["eval" , "okay"] -> assertEval opts files ["eval" , "fail"] -> assertEvalFail $ the files _ -> error $ "Unexpected: " ++ show kind assertParse :: Options -> FilePath -> Assertion assertParse _opts file = parseNixFileLoc file >>= \case Success _expr -> return () -- pure $! runST $ void $ lint opts expr Failure err -> assertFailure $ "Failed to parse " ++ file ++ ":\n" ++ show err assertParseFail :: Options -> FilePath -> Assertion assertParseFail opts file = do eres <- parseNixFileLoc file catch (case eres of Success expr -> do _ <- pure $! runST $ void $ lint opts expr assertFailure $ "Unexpected success parsing `" ++ file ++ ":\nParsed value: " ++ show expr Failure _ -> return () ) $ \(_ :: SomeException) -> return () assertLangOk :: Options -> FilePath -> Assertion assertLangOk opts file = do actual <- printNix <$> hnixEvalFile opts (file ++ ".nix") expected <- Text.readFile $ file ++ ".exp" assertEqual "" expected $ Text.pack (actual ++ "\n") assertLangOkXml :: Options -> FilePath -> Assertion assertLangOkXml opts file = do actual <- principledStringIgnoreContext . toXML <$> hnixEvalFile opts (file ++ ".nix") expected <- Text.readFile $ file ++ ".exp.xml" assertEqual "" expected actual assertEval :: Options -> [FilePath] -> Assertion assertEval _opts files = do time <- liftIO getCurrentTime let opts = defaultOptions time case delete ".nix" $ sort $ map takeExtensions files of [] -> () <$ hnixEvalFile opts (name ++ ".nix") [".exp" ] -> assertLangOk opts name [".exp.xml" ] -> assertLangOkXml opts name [".exp.disabled"] -> return () [".exp-disabled"] -> return () [".exp", ".flags"] -> do liftIO $ unsetEnv "NIX_PATH" flags <- Text.readFile (name ++ ".flags") let flags' | Text.last flags == '\n' = Text.init flags | otherwise = flags case Opts.execParserPure Opts.defaultPrefs (nixOptionsInfo time) (fixup (map Text.unpack (Text.splitOn " " flags'))) of Opts.Failure err -> errorWithoutStackTrace $ "Error parsing flags from " ++ name ++ ".flags: " ++ show err Opts.Success opts' -> assertLangOk (opts' { include = include opts' ++ [ "nix=../../../../data/nix/corepkgs" , "lang/dir4" , "lang/dir5" ] } ) name Opts.CompletionInvoked _ -> error "unused" _ -> assertFailure $ "Unknown test type " ++ show files where name = "data/nix/tests/lang/" ++ the (map (takeFileName . dropExtensions) files) fixup ("--arg" : x : y : rest) = "--arg" : (x ++ "=" ++ y) : fixup rest fixup ("--argstr" : x : y : rest) = "--argstr" : (x ++ "=" ++ y) : fixup rest fixup (x : rest) = x : fixup rest fixup [] = [] assertEvalFail :: FilePath -> Assertion assertEvalFail file = catch ?? (\(_ :: SomeException) -> return ()) $ do time <- liftIO getCurrentTime evalResult <- printNix <$> hnixEvalFile (defaultOptions time) file evalResult `seq` assertFailure $ file ++ " should not evaluate.\nThe evaluation result was `" ++ evalResult ++ "`."