packages feed

mem-info-0.4.0.0: test/MemInfo/SysInfoSpec.hs

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : MemInfo.SysInfoSpec
Copyright   : (c) 2023 Tim Emiola
Maintainer  : Tim Emiola <adetokunbo@emio.la>
SPDX-License-Identifier: BSD3
-}
module MemInfo.SysInfoSpec (spec) where

import Control.Monad (when)
import Data.GenValidity (GenValid (..))
import Data.List.NonEmpty (NonEmpty (..))
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Word (Word16, Word8)
import Fmt (build, fmt, (+|), (|+))
import MemInfo.Files.Root (initRoot, useTmp, writeRootedFile)
import MemInfo.Files.Smap (genBaseSmap, genSmapLine, genWithPss)
import MemInfo.OrphanInstances ()
import System.MemInfo.SysInfo (
  KernelVersion,
  RamFlaw (..),
  ReportBud (..),
  SwapFlaw (..),
  mkReportBud,
  parseKernelVersion,
 )
import Test.Hspec
import Test.QuickCheck
import Test.QuickCheck.Monadic (PropertyM, assert, monadicIO, pick, run)


spec :: Spec
spec = describe "module System.MemInfo.SysInfo" $ do
  describe "parseKernelVersion" $ do
    it "should parse values from Text successfully" prop_roundtripKernelVersion
  mkReportBudSpec


mkReportBudSpec :: Spec
mkReportBudSpec = do
  describe "mkReportBud" $ around useTmp $ do
    context "when the kernel is 2.4.x" $ do
      context "and there is no Inact_ ram" $ do
        it "generates the expected ReportBud" (prop_With24Kernel True)
      context "and there is Inact_ ram" $ do
        it "generates the expected ReportBud" (prop_With24Kernel False)
    context "when the kernel is earlier than 2.4" $ do
      it "generates the expected ReportBud" prop_With22Kernel
    context "when the kernel is a special 2.6 kernel" $ do
      it "generates the expected ReportBud" prop_WithSpecial26Kernel
    context "when the kernel is a later 2.6 kernel with no smap support" $ do
      it "generates the expected ReportBud" prop_WithNoSmap26Kernel
    context "when the kernel is a later 2.6 kernel with smap support" $ do
      context "and there are smap files but no Pss" $ do
        it "generates the expected ReportBud" $ prop_WithSmaps26Kernel False
      context "and there are smap files but with Pss" $ do
        it "generates the expected ReportBud" $ prop_WithSmaps26Kernel True
    context "when the kernel is later than 3.x" $ do
      context "and there are smap files" $ do
        it "generates the expected ReportBud" $ prop_WithAfter3xKernel True
      context "and there are no smap files" $ do
        it "generates the expected ReportBud" $ prop_WithAfter3xKernel False


prop_roundtripKernelVersion :: Property
prop_roundtripKernelVersion =
  within 5000000 $
    forAll genOsRelease $
      \(version, txt) -> Just version == parseKernelVersion txt


genOsRelease :: Gen (KernelVersion, Text)
genOsRelease = oneof [fromSingle, fromDouble, fromTriple]


fromTriple :: Gen (KernelVersion, Text)
fromTriple = do
  let toN = fromIntegral
      txt a b c = "" +| a |+ "." +| b |+ "." +| c |+ ""
  (x, y, z) <- genValid :: Gen (Word8, Word8, Word8)
  zSuffixed <- someWithSuffix z
  pure ((toN x, toN y, toN z), txt x y zSuffixed)


fromDouble :: Gen (KernelVersion, Text)
fromDouble = do
  (x, y) <- genValid :: Gen (Word8, Word8)
  ySuffixed <- someWithSuffix y
  let toN = fromIntegral
      txt a b = "" +| a |+ "." +| b |+ ""
  pure ((toN x, toN y, 0), txt x ySuffixed)


suffixes :: [Text]
suffixes = ["-pre", "b", "-kali", "-test"]


someWithSuffix :: Word8 -> Gen Text
someWithSuffix w = do
  addSuffix <- genValid
  s <- elements suffixes
  if addSuffix
    then pure $ "" +| w |+ "" +| s |+ ""
    else pure $ fmt $ build w


fromSingle :: Gen (KernelVersion, Text)
fromSingle = do
  x <- genValid :: Gen Word8
  pure ((fromIntegral x, 0, 0), fmt $ build x)


prop_WithAfter3xKernel :: Bool -> FilePath -> Property
prop_WithAfter3xKernel hasPss root = monadicIO $ do
  let withFlaws x =
        x
          { rbRamFlaws = if hasPss then Nothing else Just ExactForIsolatedMem
          , rbSwapFlaws = Just $ if hasPss then ExactForIsolatedSwap else NoSwap
          , rbHasSmaps = hasPss
          }
      writeSmaps txt thePid =
        when hasPss $ writeRootedFile root ("" +| thePid |+ "/smaps") txt
      genKernel = do
        patch <- genValid
        minor <- genValid
        pure (3, minor, patch)

  (_ignored, smapsTxt) <- pick genBaseSmap
  verifyMkReportBud root withFlaws genKernel $ writeSmaps smapsTxt


prop_With24Kernel :: Bool -> FilePath -> Property
prop_With24Kernel hasInactRam root = monadicIO $ do
  let withFlaws x =
        x
          { rbRamFlaws = Just $ if hasInactRam then SomeSharedMem else ExactForIsolatedMem
          , rbSwapFlaws = Just NoSwap
          }
      genKernel = do
        patch <- genValid
        pure (2, 4, patch)
      writeMemInfo txt _unused = writeRootedFile root "meminfo" txt

  memInfoTxt <- pick $ genMemInfoLines hasInactRam
  verifyMkReportBud root withFlaws genKernel $ writeMemInfo memInfoTxt


prop_WithSmaps26Kernel :: Bool -> FilePath -> Property
prop_WithSmaps26Kernel hasPss root = monadicIO $ do
  let withFlaws x =
        x
          { rbRamFlaws = if hasPss then Nothing else Just ExactForIsolatedMem
          , rbSwapFlaws = Just ExactForIsolatedSwap
          , rbHasSmaps = True
          , rbHasPss = hasPss
          }
      writeSmaps txt thePid =
        writeRootedFile root ("" +| thePid |+ "/smaps") txt
      genKernel = do
        patch <- chooseInteger (10, 100)
        pure (2, 6, fromIntegral patch)
      genSmap = if hasPss then genWithPss else genBaseSmap

  (_ignored, smapsTxt) <- pick genSmap
  verifyMkReportBud root withFlaws genKernel $ writeSmaps smapsTxt


prop_WithSpecial26Kernel :: FilePath -> Property
prop_WithSpecial26Kernel root = monadicIO $ do
  let withFlaws x =
        x
          { rbRamFlaws = Just NoSharedMem
          , rbSwapFlaws = Just NoSwap
          }
      genKernel = do
        patch <- chooseInteger (1, 9)
        pure (2, 6, fromIntegral patch)

  verifyMkReportBud root withFlaws genKernel $ const $ pure ()


prop_WithNoSmap26Kernel :: FilePath -> Property
prop_WithNoSmap26Kernel root = monadicIO $ do
  let withFlaws x =
        x
          { rbRamFlaws = Just SomeSharedMem
          , rbSwapFlaws = Just NoSwap
          }
      genKernel = do
        patch <- chooseInteger (10, 100)
        pure (2, 6, fromIntegral patch)

  verifyMkReportBud root withFlaws genKernel $ const $ pure ()


genMemInfoLines :: Bool -> Gen Text
genMemInfoLines hasInact = do
  -- reference: http://darenmatthews.com/blog/?p=2092
  (_unused1, memTotalTxt) <- genSmapLine "MemTotal"
  (_unused2, memFreeTxt) <- genSmapLine "MemFree"
  (_unused3, buffersTxt) <- genSmapLine "Buffers"
  (_unused4, inactTxt) <- genSmapLine "Inact_Target" -- or Inact_{Clean,Dirty}
  let baselines = [memTotalTxt, memFreeTxt, buffersTxt]
      ls = if hasInact then baselines <> [inactTxt] else baselines
  pure $ Text.unlines ls


prop_With22Kernel :: FilePath -> Property
prop_With22Kernel root = monadicIO $ do
  let withFlaws x =
        x
          { rbRamFlaws = Just ExactForIsolatedMem
          , rbSwapFlaws = Just NoSwap
          }
      genKernel = do
        patch <- genValid
        pure (2, 2, patch)
  verifyMkReportBud root withFlaws genKernel $ const $ pure ()


verifyMkReportBud ::
  FilePath ->
  (ReportBud -> ReportBud) ->
  Gen KernelVersion ->
  (Word16 -> IO ()) ->
  PropertyM IO ()
verifyMkReportBud root changeBud genKernel writeFiles = do
  (thePid, version, want) <- pick $ genExpectedBud changeBud genKernel root
  bud <- run $ do
    initRoot root version
    writeFiles thePid
    mkReportBud root (fromIntegral thePid :| [])
  assert (bud == Just want)


genExpectedBud ::
  (ReportBud -> ReportBud) ->
  Gen KernelVersion ->
  FilePath ->
  Gen (Word16, KernelVersion, ReportBud)
genExpectedBud changeBud genKernel root = do
  thePid <- genValidProcId
  rbKernel <- genKernel
  let theBud =
        ReportBud
          { rbPids = fromIntegral thePid :| []
          , rbKernel
          , rbHasPss = False
          , rbHasSwapPss = False
          , rbHasSmaps = False
          , rbRamFlaws = Nothing
          , rbSwapFlaws = Nothing
          , rbProcRoot = root
          }
  pure (thePid, rbKernel, changeBud theBud)


genValidProcId :: Gen Word16
genValidProcId = genValid `suchThat` (> 1)