packages feed

tala-compiler-0.1.0: test/DiagnosticSideChannelTests.hs

-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1

module DiagnosticSideChannelTests (diagnosticSideChannelTests) where

import Control.Exception (finally)
import Data.ByteString qualified as ByteString
import System.Directory (doesFileExist, getTemporaryDirectory, removeFile)
import System.FilePath ((</>))
import Progmasoft.Tala.AST
import Progmasoft.Tala.Diagnostic
import Progmasoft.Tala.Diagnostic.Protocol
import Progmasoft.Tala.Diagnostic.SideChannel

diagnosticSideChannelTests :: [(String, IO Bool)]
diagnosticSideChannelTests =
    [ ("diagnostic side channel writes the stable empty success document", writesEmptyDocument)
    , ("diagnostic side channel writes an error document", writesErrorDocument)
    , ("diagnostic side channel overwrites stale bytes", overwritesStaleBytes)
    , ("diagnostic side channel rejects an empty path", rejectsEmptyPath)
    , ("diagnostic side channel reports unavailable parent directories", reportsWriteFailure)
    , ("diagnostic side channel rejects invalid source coordinates before I/O", rejectsInvalidModel)
    ]

writesEmptyDocument :: IO Bool
writesEmptyDocument = withDiagnosticPath "empty" $ \path -> do
    written <- writeDiagnosticFile path []
    bytes <- ByteString.readFile path
    pure
        ( written == Right ()
            && decodeDiagnosticDocument defaultDiagnosticProtocolLimits bytes == Right (DiagnosticDocument [])
        )

writesErrorDocument :: IO Bool
writesErrorDocument = withDiagnosticPath "error" $ \path -> do
    written <- writeDiagnosticFile path [sampleDiagnostic]
    bytes <- ByteString.readFile path
    pure $ case (written, decodeDiagnosticDocument defaultDiagnosticProtocolLimits bytes) of
        (Right (), Right (DiagnosticDocument [record])) ->
            recordStage record == ParserStage
                && recordSeverity record == ProtocolError
                && recordCode record == "TAP100"
                && recordMessage record == "expected declaration"
                && recordPrimary record == Just (DiagnosticLocation "Main.tala" 3 8 3 13)
        _ -> False

overwritesStaleBytes :: IO Bool
overwritesStaleBytes = withDiagnosticPath "stale" $ \path -> do
    ByteString.writeFile path (ByteString.replicate 256 0xff)
    written <- writeDiagnosticFile path []
    bytes <- ByteString.readFile path
    pure
        ( written == Right ()
            && ByteString.length bytes == 12
            && decodeDiagnosticDocument defaultDiagnosticProtocolLimits bytes == Right (DiagnosticDocument [])
        )

rejectsEmptyPath :: IO Bool
rejectsEmptyPath = do
    result <- writeDiagnosticFile "" []
    pure $ case result of
        Left (SideChannelWriteError "" _) -> True
        _ -> False

reportsWriteFailure :: IO Bool
reportsWriteFailure = do
    temporary <- getTemporaryDirectory
    let path = temporary </> "xide-side-channel-missing-parent" </> "diagnostics.vxdg"
    result <- writeDiagnosticFile path []
    pure $ case result of
        Left (SideChannelWriteError failedPath _) -> failedPath == path
        _ -> False

rejectsInvalidModel :: IO Bool
rejectsInvalidModel = withDiagnosticPath "invalid" $ \path -> do
    let invalid =
            sampleDiagnostic
                { diagnosticSpan =
                    Just (SourceSpan "Main.tala" (SourcePosition 0 1) (SourcePosition 1 1))
                }
    result <- writeDiagnosticFile path [invalid]
    exists <- doesFileExist path
    pure $ case result of
        Left (SideChannelModelError _) -> not exists
        _ -> False

sampleDiagnostic :: Diagnostic
sampleDiagnostic =
    Diagnostic
        ParserStage
        Error
        "TAP100"
        (Just (SourceSpan "Main.tala" (SourcePosition 4 9) (SourcePosition 4 14)))
        "expected declaration"

withDiagnosticPath :: String -> (FilePath -> IO Bool) -> IO Bool
withDiagnosticPath suffix action = do
    temporary <- getTemporaryDirectory
    let path = temporary </> ("tala-diagnostics-" ++ suffix ++ ".vxdg")
        cleanup = do
            exists <- doesFileExist path
            if exists then removeFile path else pure ()
    cleanup
    action path `finally` cleanup