taffybar-4.1.2: test/unit/System/Taffybar/AppearanceSpec.hs
module System.Taffybar.AppearanceSpec (spec) where
import Control.Monad (when)
import System.Directory (doesFileExist, findExecutable, makeAbsolute)
import System.Exit (ExitCode (..))
import System.FilePath ((</>))
import System.Timeout (timeout)
import qualified Codec.Picture as JP
import qualified Data.ByteString.Lazy as BL
import Test.Hspec
import UnliftIO.Directory (createDirectoryIfMissing)
import UnliftIO.Environment (lookupEnv)
import UnliftIO.Temporary (withSystemTempDirectory)
import System.Process.Typed
( inherit
, proc
, setStderr
, setStdout
, stopProcess
, waitExitCode
, withProcessTerm
)
import System.Taffybar.Test.DBusSpec (withTestDBus)
import System.Taffybar.Test.UtilSpec (withEnv, withSetEnv)
import System.Taffybar.Test.XvfbSpec (setDefaultDisplay_, withXvfb)
spec :: Spec
spec = aroundAll withIntegrationEnv $ do
it "renders a bar under an EWMH window manager" $ \env -> do
goldenFile <- makeAbsolute "test/data/appearance-ewmh-bar.png"
actualPng <- renderBarScreenshot env
shouldUpdate <- lookupEnv "TAFFYBAR_UPDATE_GOLDENS"
case shouldUpdate of
Just _ -> do
BL.writeFile goldenFile actualPng
createDirectoryIfMissing True "dist"
BL.writeFile "dist/appearance-actual.png" actualPng
Nothing -> do
goldenPng <- BL.readFile goldenFile
let actualImg = decodePngRGBA8 "actual" actualPng
goldenImg = decodePngRGBA8 "golden" goldenPng
when (actualImg /= goldenImg) $ do
createDirectoryIfMissing True "dist"
BL.writeFile "dist/appearance-actual.png" actualPng
BL.writeFile "dist/appearance-golden.png" goldenPng
expectationFailure $
"Appearance golden mismatch: " ++ goldenFile ++
" (wrote dist/appearance-actual.png and dist/appearance-golden.png)"
newtype Env = Env
{ envTmpDir :: FilePath
}
withIntegrationEnv :: ActionWith Env -> IO ()
withIntegrationEnv action =
withXvfb $ \dn ->
setDefaultDisplay_ dn $
withTestDBus $
withSystemTempDirectory "taffybar-appearance" $ \tmp -> do
let runtimeDir = tmp </> "xdg-run"
createDirectoryIfMissing True runtimeDir
-- Keep user/system config out of the test run and reduce variability.
withEnv
[ ("WAYLAND_DISPLAY", const Nothing)
, ("HYPRLAND_INSTANCE_SIGNATURE", const Nothing)
] $
withSetEnv
[ ("GDK_BACKEND", "x11")
, ("GDK_SCALE", "1")
, ("GDK_DPI_SCALE", "1")
, ("GTK_CSD", "0")
, ("GTK_THEME", "Adwaita")
, ("XDG_SESSION_TYPE", "x11")
, ("XDG_RUNTIME_DIR", runtimeDir)
, ("NO_AT_BRIDGE", "1")
, ("GSETTINGS_BACKEND", "memory")
, ("HOME", tmp)
, ("XDG_CONFIG_HOME", tmp </> "xdg-config")
, ("XDG_CACHE_HOME", tmp </> "xdg-cache")
, ("XDG_DATA_HOME", tmp </> "xdg-data")
] $
action (Env { envTmpDir = tmp })
renderBarScreenshot :: Env -> IO BL.ByteString
renderBarScreenshot Env { envTmpDir = tmp } = do
exePath <-
findComponentExecutable
"taffybar-appearance-snap"
[ "dist/build/taffybar-appearance-snap/taffybar-appearance-snap"
]
cssPath <- makeAbsolute "test/data/appearance-test.css"
outPath <- makeAbsolute (tmp </> "appearance-actual.png")
let pc =
setStdout inherit $
setStderr inherit $
proc exePath ["--out", outPath, "--css", cssPath]
withProcessTerm pc $ \p -> do
mEc <- timeout 60_000_000 (waitExitCode p)
case mEc of
Nothing -> do
stopProcess p
expectationFailure "Timed out running taffybar-appearance-snap"
Just ExitSuccess -> pure ()
Just (ExitFailure n) ->
expectationFailure ("taffybar-appearance-snap exited with " ++ show n)
BL.readFile outPath
findComponentExecutable :: String -> [FilePath] -> IO FilePath
findComponentExecutable name localCandidates = do
mexe <- findExecutable name
case mexe of
Just exe -> makeAbsolute exe
Nothing -> go localCandidates
where
go [] = fail (name ++ " not found on PATH")
go (p:ps) = do
exists <- doesFileExist p
if exists then makeAbsolute p else go ps
decodePngRGBA8 :: String -> BL.ByteString -> JP.Image JP.PixelRGBA8
decodePngRGBA8 label bs =
case JP.decodePng (BL.toStrict bs) of
Left err -> error (label ++ " PNG decode failed: " ++ err)
Right dyn -> JP.convertRGBA8 dyn