packages feed

swarm-0.4: test/unit/TestScoring.hs

{-# LANGUAGE OverloadedStrings #-}

-- | High score records
module TestScoring where

import Data.Text.IO qualified as TIO
import Data.Time.Calendar.OrdinalDate
import Data.Time.LocalTime
import Swarm.Game.CESK (TickNumber (..))
import Swarm.Game.Scenario.Scoring.Best
import Swarm.Game.Scenario.Scoring.CodeSize
import Swarm.Game.Scenario.Scoring.ConcreteMetrics
import Swarm.Game.Scenario.Scoring.GenericMetrics
import Swarm.Language.Module
import Swarm.Language.Pipeline
import Swarm.Language.Syntax
import System.FilePath ((</>))
import Test.Tasty
import Test.Tasty.HUnit
import Witch (into)

baseTestPath :: FilePath
baseTestPath = "data/test/language-snippets/code-size"

testHighScores :: TestTree
testHighScores =
  testGroup
    "Scoring"
    [ testGroup
        "Code size"
        [ compareAstSize 1 "single-move-bare.sw"
        , compareAstSize 2 "single-move-def.sw"
        , compareAstSize 3 "single-move-let-with-invocation.sw"
        , compareAstSize 5 "double-move-let-with-invocation.sw"
        , compareAstSize 6 "single-move-def-with-invocation.sw"
        , compareAstSize 8 "double-move-def-with-invocation.sw"
        , compareAstSize 28 "single-def-two-args-recursive.sw"
        , compareAstSize 36 "single-def-two-args-recursive-with-invocation.sw"
        ]
    , testGroup
        "Precedence"
        [ testGroup
            "Single metrics"
            [ testCase
                "Attempted long > Attempted short"
                $ assertEqual "Longer is better for incomplete games" (Metric Attempted (5 :: Int))
                $ chooseBetter pure (Metric Attempted 3) (Metric Attempted 5)
            , testCase
                "Completed short > Completed long"
                $ assertEqual "Shorter is better for completed games" (Metric Completed (3 :: Int))
                $ chooseBetter pure (Metric Completed 3) (Metric Completed 5)
            ]
        , testGroup
            "Grouped metrics"
            [ betterReplTimeAfterCodeSizeRecord
            , betterCodeWorseTime
            ]
        ]
    ]

compareAstSize :: Int -> FilePath -> TestTree
compareAstSize expectedSize path = testCase (unwords ["size of", path]) $ do
  contents <- TIO.readFile $ baseTestPath </> path
  ProcessedTerm (Module stx _) _ _ <- case processTermEither contents of
    Right x -> return x
    Left y -> assertFailure (into @String y)
  let actualSize = measureAstSize stx
  assertEqual "incorrect size" expectedSize actualSize

betterReplTimeAfterCodeSizeRecord :: TestTree
betterReplTimeAfterCodeSizeRecord =
  testCase
    "new repl win after code size record"
    $ assertEqual "incorrect" newExpectedBest newActualBest
 where
  mkZonedTime t = ZonedTime (LocalTime (fromOrdinalDate 2023 1) (TimeOfDay 0 t 0)) utc
  newRunWithoutCodeSize =
    Metric Completed $
      ProgressStats (mkZonedTime 1) $
        AttemptMetrics
          (DurationMetrics 1 $ TickNumber 1)
          Nothing

  oldCompletedRunWithCodeSize =
    Metric Completed $
      ProgressStats (mkZonedTime 0) $
        AttemptMetrics
          (DurationMetrics 2 $ TickNumber 2)
          (Just $ ScenarioCodeMetrics 1 1)

  oldBestWithCodeSize =
    BestRecords
      oldCompletedRunWithCodeSize
      oldCompletedRunWithCodeSize
      oldCompletedRunWithCodeSize
      oldCompletedRunWithCodeSize

  newExpectedBest =
    BestRecords
      newRunWithoutCodeSize
      newRunWithoutCodeSize
      oldCompletedRunWithCodeSize
      oldCompletedRunWithCodeSize

  newActualBest =
    updateBest
      newRunWithoutCodeSize
      oldBestWithCodeSize

betterCodeWorseTime :: TestTree
betterCodeWorseTime =
  testCase
    "improvement upon code size with a worse time"
    $ assertEqual "incorrect" newExpectedBests newActualBests
 where
  mkZonedTime t = ZonedTime (LocalTime (fromOrdinalDate 2023 1) (TimeOfDay 0 t 0)) utc
  newRunBetterCodeSize =
    Metric Completed $
      ProgressStats (mkZonedTime 1) $
        AttemptMetrics
          (DurationMetrics 2 $ TickNumber 2)
          (Just $ ScenarioCodeMetrics 1 1)

  oldRunPoorCodeSize =
    Metric Completed $
      ProgressStats (mkZonedTime 0) $
        AttemptMetrics
          (DurationMetrics 1 $ TickNumber 1)
          (Just $ ScenarioCodeMetrics 2 2)

  oldBests =
    BestRecords
      oldRunPoorCodeSize
      oldRunPoorCodeSize
      oldRunPoorCodeSize
      oldRunPoorCodeSize

  newExpectedBests =
    BestRecords
      oldRunPoorCodeSize
      oldRunPoorCodeSize
      newRunBetterCodeSize
      newRunBetterCodeSize

  newActualBests =
    updateBest
      newRunBetterCodeSize
      oldBests