diff --git a/.gitignore b/.gitignore index 2c9e2d5..e954cbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.bkp .vscode/ node_modules/ +test/integration-tests/out/ vscode-extension/dist/ vscode-extension/result dist-newstyle/ diff --git a/haskell-debugger.cabal b/haskell-debugger.cabal index 70c60a9..b2ec668 100644 --- a/haskell-debugger.cabal +++ b/haskell-debugger.cabal @@ -76,6 +76,8 @@ library directory >= 1.3.9.0 && < 1.4, exceptions >= 0.10.9 && < 0.11, bytestring >= 0.12.1 && < 0.13, + cryptohash-sha1, + base16-bytestring, aeson >= 2.2.3 && < 2.3, hie-bios >= 0.15 && < 0.18 @@ -110,6 +112,7 @@ executable hdb implicit-hie ^>=0.1.4.0, transformers, + time, directory >= 1.3.9 && < 1.4, async >= 2.2.5 && < 2.3, text >= 2.1 && < 2.3, diff --git a/haskell-debugger/GHC/Debugger/Monad.hs b/haskell-debugger/GHC/Debugger/Monad.hs index 38b9d8a..39a3eaf 100644 --- a/haskell-debugger/GHC/Debugger/Monad.hs +++ b/haskell-debugger/GHC/Debugger/Monad.hs @@ -1,4 +1,12 @@ -{-# LANGUAGE BangPatterns, CPP, GeneralizedNewtypeDeriving, NamedFieldPuns, TupleSections, LambdaCase, OverloadedRecordDot, TypeApplications #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} + module GHC.Debugger.Monad where import Prelude hiding (mod) @@ -108,15 +116,12 @@ runDebugger :: Handle -- ^ The handle to which GHC's output is logged. The d -> IO a runDebugger dbg_out rootDir compDir libdir units ghcInvocation' mainFp conf (Debugger action) = do let ghcInvocation = filter (\case ('-':'B':_) -> False; _ -> True) ghcInvocation' - GHC.runGhc (Just libdir) $ do -- Workaround #4162 _ <- liftIO $ installHandler sigINT Default Nothing dflags0 <- GHC.getSessionDynFlags - let dflags1 = dflags0 { GHC.ghcMode = GHC.CompManager - , GHC.backend = GHC.interpreterBackend , GHC.ghcLink = GHC.LinkInMemory , GHC.verbosity = 1 , GHC.canUseColor = conf.supportsANSIStyling @@ -128,6 +133,8 @@ runDebugger dbg_out rootDir compDir libdir units ghcInvocation' mainFp conf (Deb `GHC.gopt_set` GHC.Opt_IgnoreHpcChanges `GHC.gopt_set` GHC.Opt_UseBytecodeRatherThanObjects `GHC.gopt_set` GHC.Opt_InsertBreakpoints + & setBytecodeBackend + & enableByteCodeGeneration GHC.modifyLogger $ -- Override the logger to output to the given handle @@ -163,6 +170,7 @@ runDebugger dbg_out rootDir compDir libdir units ghcInvocation' mainFp conf (Deb runReaderT action =<< initialDebuggerState + -- | The logger action used to log GHC output debuggerLoggerAction :: Handle -> LogAction debuggerLoggerAction h a b c d = do diff --git a/haskell-debugger/GHC/Debugger/Session.hs b/haskell-debugger/GHC/Debugger/Session.hs index d26bf67..2b00a0c 100644 --- a/haskell-debugger/GHC/Debugger/Session.hs +++ b/haskell-debugger/GHC/Debugger/Session.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE DerivingStrategies, CPP #-} +{-# LANGUAGE DerivingStrategies, CPP, RecordWildCards #-} -- | Initialise the GHC session for one or more home units. -- @@ -10,11 +10,31 @@ module GHC.Debugger.Session ( TargetDetails(..), Target(..), toGhcTarget, + CacheDirs(..), + getCacheDirs, + -- * DynFlags modifications + setWorkingDirectory, + setCacheDirs, + setBytecodeBackend, + enableByteCodeGeneration, ) where import Control.Monad import Control.Monad.IO.Class +import qualified Crypto.Hash.SHA1 as H +import qualified Data.ByteString.Base16 as B16 +import qualified Data.ByteString.Char8 as B +import Data.Function +import qualified Data.List.NonEmpty as NonEmpty +import qualified Data.Map as Map +import qualified Data.List as L +import qualified Data.Containers.ListUtils as L +import GHC.ResponseFile (expandResponse) +import HIE.Bios.Environment as HIE +import System.FilePath +import qualified System.Directory as Directory +import qualified System.Environment as Env import qualified GHC import GHC.Driver.DynFlags as GHC @@ -30,14 +50,6 @@ import GHC.Driver.Env import GHC.Types.SrcLoc import Language.Haskell.Syntax.Module.Name -import qualified Data.List.NonEmpty as NonEmpty -import qualified Data.Map as Map -import qualified Data.List as L -import qualified Data.Containers.ListUtils as L -import GHC.ResponseFile (expandResponse) -import HIE.Bios.Environment as HIE -import System.FilePath - -- | Throws if package flags are unsatisfiable parseHomeUnitArguments :: GhcMonad m => FilePath -- ^ Main entry point function @@ -80,13 +92,16 @@ parseHomeUnitArguments cfp compRoot units theOpts dflags rootDir = do initOne args initOne this_opts = do (dflags', targets') <- addCmdOpts this_opts dflags - let targets = HIE.makeTargetsAbsolute root targets' root = case workingDirectory dflags' of Nothing -> compRoot Just wdir -> compRoot wdir + cacheDirs <- liftIO $ getCacheDirs (takeFileName root) this_opts let dflags'' = setWorkingDirectory root $ + setCacheDirs cacheDirs $ + enableByteCodeGeneration $ + setBytecodeBackend $ makeDynFlagsAbsolute compRoot -- makeDynFlagsAbsolute already accounts for workingDirectory dflags' return (dflags'', targets) @@ -228,9 +243,78 @@ mkSimpleTarget df fp = GHC.Target (GHC.TargetFile fp Nothing) True (homeUnitId_ hscSetUnitEnv :: UnitEnv -> HscEnv -> HscEnv hscSetUnitEnv ue env = env { hsc_unit_env = ue } +-- ---------------------------------------------------------------------------- +-- Session cache directory +-- ---------------------------------------------------------------------------- + +data CacheDirs = CacheDirs + { hiCacheDir :: FilePath + , byteCodeCacheDir :: FilePath + , hieCacheDir :: FilePath + , objCacheDir :: FilePath + } + +getCacheDirs :: String -> [String] -> IO CacheDirs +getCacheDirs prefix opts = do + mCacheDir <- Env.lookupEnv "HDB_CACHE_DIR" + rootDir <- case mCacheDir of + Just dir -> pure dir + Nothing -> + Directory.getXdgDirectory Directory.XdgCache "hdb" + let sessionCacheDir = rootDir prefix ++ "-" ++ opts_hash + Directory.createDirectoryIfMissing True sessionCacheDir + pure CacheDirs + { hiCacheDir = sessionCacheDir + , byteCodeCacheDir = sessionCacheDir + , hieCacheDir = sessionCacheDir + , objCacheDir = sessionCacheDir + } + where + -- Create a unique folder per set of different GHC options, assuming that each different set of + -- GHC options will create incompatible interface files. + opts_hash = B.unpack $ B16.encode $ H.finalize $ H.updates H.init (map B.pack opts) + +-- ---------------------------------------------------------------------------- +-- Modification of DynFlags +-- ---------------------------------------------------------------------------- + setWorkingDirectory :: FilePath -> DynFlags -> DynFlags setWorkingDirectory p d = d { workingDirectory = Just p } +setCacheDirs :: CacheDirs -> DynFlags -> DynFlags +setCacheDirs CacheDirs{..} flags = flags + { hiDir = Just hiCacheDir + , hieDir = Just hieCacheDir + , objectDir = Just objCacheDir +#if MIN_VERSION_ghc(9,14,2) + , bytecodeDir = Just byteCodeCacheDir +#endif + } + +-- | If the compiler supports `.gbc` files (>= 9.14.2), then persist these +-- artefacts to disk. +enableByteCodeGeneration :: DynFlags -> DynFlags +enableByteCodeGeneration dflags = +#if MIN_VERSION_ghc(9,14,2) + dflags + & flip gopt_unset Opt_ByteCodeAndObjectCode + & flip gopt_set Opt_ByteCode + & flip gopt_set Opt_WriteByteCode + & flip gopt_set Opt_WriteInterface +#else + dflags +#endif + +setBytecodeBackend :: DynFlags -> DynFlags +setBytecodeBackend dflags = dflags + { +#if MIN_VERSION_ghc(9,14,2) + backend = GHC.bytecodeBackend +#else + backend = GHC.interpreterBackend +#endif + } + -- ---------------------------------------------------------------------------- -- Utils that we need, but don't want to incur an additional dependency for. -- ---------------------------------------------------------------------------- diff --git a/hdb/Development/Debug/Adapter/Logger.hs b/hdb/Development/Debug/Adapter/Logger.hs index eb06107..3f59618 100644 --- a/hdb/Development/Debug/Adapter/Logger.hs +++ b/hdb/Development/Debug/Adapter/Logger.hs @@ -7,6 +7,7 @@ module Development.Debug.Adapter.Logger ( Severity (..), WithSeverity (..), cmap, + cmapIO, cmapWithSev, -- * Pretty printing of logs @@ -14,17 +15,27 @@ module Development.Debug.Adapter.Logger ( renderWithSeverity, renderPretty, renderSeverity, + renderWithTimestamp, ) where +import Control.Monad.IO.Class +import Control.Monad ((>=>)) import Colog.Core (LogAction (..), Severity (..), WithSeverity (..)) import Colog.Core.Action (cmap) import Data.Text (Text) +import qualified Data.Text as T import Prettyprinter import Prettyprinter.Render.Text (renderStrict) +import Data.Time (defaultTimeLocale, formatTime, getCurrentTime) cmapWithSev :: (a -> b) -> LogAction m (WithSeverity b) -> LogAction m (WithSeverity a) cmapWithSev f = cmap (fmap f) +cmapIO :: MonadIO m => (a -> IO b) -> LogAction m b -> LogAction m a +cmapIO f LogAction{ unLogAction } = + LogAction + { unLogAction = (liftIO . f) >=> unLogAction } + renderPrettyWithSeverity :: Pretty a => WithSeverity a -> Text renderPrettyWithSeverity = renderWithSeverity renderPretty @@ -40,6 +51,14 @@ renderPretty a = in docToText (pretty a) +renderWithTimestamp :: Text -> IO Text +renderWithTimestamp msg = do + t <- getCurrentTime + let timeStamp = utcTimeToText t + pure $ "[" <> timeStamp <> "]" <> msg + where + utcTimeToText utcTime = T.pack $ formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S%6QZ" utcTime + renderSeverity :: Severity -> Text renderSeverity = \ case Debug -> "[DEBUG]" diff --git a/hdb/Main.hs b/hdb/Main.hs index 02c474f..410d590 100644 --- a/hdb/Main.hs +++ b/hdb/Main.hs @@ -23,7 +23,6 @@ import qualified Data.Text as T import qualified Data.Text.IO as T import GHC.IO.Handle.FD - defaultStdoutForwardingAction :: T.Text -> IO () defaultStdoutForwardingAction line = do T.hPutStrLn stderr ("[INTERCEPTED STDOUT] " <> line) @@ -37,8 +36,10 @@ main = do withInterceptedStdoutForwarding defaultStdoutForwardingAction $ \realStdout -> do hSetBuffering realStdout LineBuffering l <- handleLogger realStdout - let loggerWithSev = cmap (renderWithSeverity id) l - runDAPServerWithLogger (cmap renderDAPLog l) config (talk loggerWithSev) + let + timeStampLogger = cmapIO renderWithTimestamp l + loggerWithSev = cmap (renderWithSeverity id) timeStampLogger + runDAPServerWithLogger (cmap renderDAPLog timeStampLogger) config (talk loggerWithSev) -- | Fetch config from environment, fallback to sane defaults getConfig :: Int -> IO ServerConfig diff --git a/test/integration-tests/.mocharc.json b/test/integration-tests/.mocharc.json new file mode 100644 index 0000000..2bac0ac --- /dev/null +++ b/test/integration-tests/.mocharc.json @@ -0,0 +1,5 @@ +{ + "extension": ["ts"], + "spec": "test/**/*.test.ts", + "require": "ts-node/register" +} diff --git a/test/integration-tests/Makefile b/test/integration-tests/Makefile index 3b6460d..a97c23f 100644 --- a/test/integration-tests/Makefile +++ b/test/integration-tests/Makefile @@ -2,16 +2,16 @@ all: test .PHONY: clean test -node := $(shell nix-shell -p nodejs --run 'which node') +node := $(shell nix develop --command bash -c "which node") clean: rm -rf ./node_modules node_modules: - nix-shell -p 'nodejs' --run 'npm install' + nix develop --command bash -c "npm install && npm run compile" test: node_modules # PATH=$(dir $(GHC)):$(dir $(DEBUGGER)):$$PATH ./node_modules/.bin/mocha -f 'allow arbitrarily deep' # nix-shell -p nodejs --run 'PATH=$(dir $(GHC)):$(dir $(DEBUGGER)):$$PATH ./node_modules/.bin/mocha' @echo "NODE: $(node)" - PATH=$(dir $(GHC)):$(dir $(DEBUGGER)):$(dir $(node)):$$PATH ./node_modules/.bin/mocha --parallel + nix develop --command bash -c "PATH=$(dir $(GHC)):$(dir $(DEBUGGER)):$(dir $(node)):$$PATH npm run test" diff --git a/test/integration-tests/flake.lock b/test/integration-tests/flake.lock new file mode 100644 index 0000000..ab045cb --- /dev/null +++ b/test/integration-tests/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1756128520, + "narHash": "sha256-R94HxJBi+RK1iCm8Y4Q9pdrHZl0GZoDPIaYwjxRNPh4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c53baa6685261e5253a1c355a1b322f82674a824", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/test/integration-tests/flake.nix b/test/integration-tests/flake.nix new file mode 100644 index 0000000..36dea37 --- /dev/null +++ b/test/integration-tests/flake.nix @@ -0,0 +1,19 @@ +{ + description = "Integration tests for hdb"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; # or unstable + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let pkgs = nixpkgs.legacyPackages.${system}; in { + devShells.default = pkgs.mkShell { + buildInputs = [ + pkgs.nodejs + pkgs.nodePackages.npm + ]; + }; + }); +} diff --git a/test/integration-tests/package-lock.json b/test/integration-tests/package-lock.json index c49362f..09323fa 100644 --- a/test/integration-tests/package-lock.json +++ b/test/integration-tests/package-lock.json @@ -10,6 +10,25 @@ "dependencies": { "@vscode/debugadapter-testsupport": "^1.68", "mocha": "^11.2.2" + }, + "devDependencies": { + "@types/mocha": "^10.0.10", + "@types/node": "^24.3.0", + "ts-node": "^10.9.2", + "typescript": "^5.9.2" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" } }, "node_modules/@isaacs/cliui": { @@ -29,6 +48,34 @@ "node": ">=12" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -39,6 +86,51 @@ "node": ">=14" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } + }, "node_modules/@vscode/debugadapter-testsupport": { "version": "1.68.0", "resolved": "https://registry.npmjs.org/@vscode/debugadapter-testsupport/-/debugadapter-testsupport-1.68.0.tgz", @@ -57,6 +149,32 @@ "integrity": "sha512-2J27dysaXmvnfuhFGhfeuxfHRXunqNPxtBoR3koiTOA9rdxWNDTa1zIFLCFMSHJ9MPTPKFcBeblsyaCJCIlQxg==", "license": "MIT" }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -84,6 +202,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -256,6 +381,13 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -544,6 +676,13 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, "node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -898,6 +1037,88 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1099,6 +1320,16 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/test/integration-tests/package.json b/test/integration-tests/package.json index a913525..1e7e753 100644 --- a/test/integration-tests/package.json +++ b/test/integration-tests/package.json @@ -7,6 +7,13 @@ "mocha": "^11.2.2" }, "scripts": { - "test": "mocha" + "compile": "tsc", + "test": "mocha --parallel" + }, + "devDependencies": { + "@types/mocha": "^10.0.10", + "@types/node": "^24.3.0", + "ts-node": "^10.9.2", + "typescript": "^5.9.2" } } diff --git a/test/integration-tests/test/adapter.test.js b/test/integration-tests/test/adapter.test.ts similarity index 96% rename from test/integration-tests/test/adapter.test.js rename to test/integration-tests/test/adapter.test.ts index 7e5bcc0..b21141d 100644 --- a/test/integration-tests/test/adapter.test.js +++ b/test/integration-tests/test/adapter.test.ts @@ -2,11 +2,12 @@ import { DebugClient } from '@vscode/debugadapter-testsupport'; import * as cp from 'child_process'; import * as net from 'net'; import { tmpdir } from 'node:os'; -import { join, normalize } from 'node:path'; +import { join } from 'node:path'; import { mkdtempSync, cpSync, realpathSync } from 'node:fs'; import assert from 'assert'; +import { describe, beforeEach, afterEach, it } from 'mocha'; -function getFreePort() { +function getFreePort(): Promise { return new Promise((resolve, reject) => { const server = net.createServer(); server.listen(0, () => { @@ -22,7 +23,7 @@ function getFreePort() { }); } -var dc; +var dc: DebugClient; let debuggerProcess; describe("Debug Adapter Tests", function () { @@ -31,10 +32,15 @@ describe("Debug Adapter Tests", function () { const ghc_version = cp.execSync('ghc --numeric-version').toString().trim() beforeEach( () => getFreePort().then(port => { + const cacheDir = mkHermeticCacheDir(); + const hdbEnv = { + env: { + ...process.env, HDB_CACHE_DIR: cacheDir + } + }; + debuggerProcess = cp.spawn('hdb', ['--server', '--port', port.toString()], hdbEnv); - debuggerProcess = cp.spawn('hdb', ['--server', '--port', port.toString()]); - - const ready = new Promise((resolve, reject) => { + const ready: Promise = new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error("haskell-debugger did not signal readiness in time")); }, 15000); // 15 second timeout @@ -72,21 +78,25 @@ describe("Debug Adapter Tests", function () { dc.stop() }); - const mkHermetic = (path) => { - const tmp = mkdtempSync(join(tmpdir(), "haskell-debugger-")) + path + const mkHermetic = (path: string) => { + const tmp = mkdtempSync(join(tmpdir(), "hdb-")) + path const data = process.cwd() + path; cpSync(data, tmp, { recursive: true }) // Copy data contents to temp directory return realpathSync(tmp) } + const mkHermeticCacheDir = () => { + const tmp = mkdtempSync(join(tmpdir(), "hdb-cache-")) + return realpathSync(tmp) + } + const mkConfig = config => { // Run tests on the temporary directory. This avoids issues with // hie-bios finding bad project roots because of cabal.projects in the // file system. const tmp = mkHermetic(config.projectRoot) - config.projectRoot = tmp; - + config.projectRoot = tmp return config } @@ -175,7 +185,7 @@ describe("Debug Adapter Tests", function () { const stResp = await dc.stackTraceRequest({ threadId: 0 }); const sf0 = stResp.body.stackFrames[0]; const scResp = await dc.scopesRequest({ frameId: sf0.id }); - const localsScope = scResp.body.scopes.find(scope => scope.name == "Locals"); + const localsScope = scResp.body.scopes.find(scope => scope.name == "Locals")!!; const variablesResp = await dc.variablesRequest({ variablesReference: localsScope.variablesReference }); const variables = variablesResp.body.variables; return { diff --git a/test/integration-tests/tsconfig.json b/test/integration-tests/tsconfig.json new file mode 100644 index 0000000..b4e2a92 --- /dev/null +++ b/test/integration-tests/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "./out", + "rootDir": "./test", + "esModuleInterop": true, + }, + "include": ["test/**/*"], + "exclude": ["node_modules", "data"] +}