packages feed

swarm-0.7.0.0: test/integration/TestRecipeCoverage.hs

{-# LANGUAGE OverloadedStrings #-}

-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Ensure recipe coverage for all entities.
module TestRecipeCoverage (testRecipeCoverage) where

import Control.Lens (view)
import Data.List qualified as List
import Data.Set qualified as Set
import Data.Text qualified as T
import Swarm.Game.Entity (Entity, EntityName, entityName)
import Swarm.Game.Recipe.Graph qualified as RG
import Swarm.Util (applyWhen, quote)
import Test.Tasty
import Test.Tasty.ExpectedFailure (expectFailBecause)
import Test.Tasty.HUnit

-- | Generate test tree to check that each entity either has a reachable
--   recipe or is in the world or starting base robot inventory.
--
-- If you added a recipe, thank you, please remove the entity from the list
-- of known uncraftable entities.
-- If you are not sure why an entity with a recipe is unreachable, check out
-- the dot graph of entity recipes in 'Swarm.Doc.Gen' that this test uses.
testRecipeCoverage :: IO TestTree
testRecipeCoverage = do
  graphData <- RG.classicScenarioRecipeGraph
  let sortE = List.sortOn (T.unpack . view entityName)
      allEntities = sortE . Set.toList $ RG.allEntities graphData
      nonCovered = getNonCoveredEntities graphData
  return . testGroup "Ensure all entities have recipes" $
    map (\e -> expectNonCovered e $ checkCoverage nonCovered e) allEntities
 where
  checkCoverage :: Set.Set Entity -> Entity -> TestTree
  checkCoverage s e =
    let name = view entityName e
     in testCase (T.unpack name) $ do
          assertBool (errMessage name) (name `elem` RG.ignoredEntities || e `Set.notMember` s)
   where
    errMessage missing = T.unpack $ "Can not make " <> quote missing <> " from starting entities."

expectNonCovered :: Entity -> TestTree -> TestTree
expectNonCovered e =
  let name = T.toCaseFold (view entityName e)
   in applyWhen (name `elem` nonCoveredList) $
        expectFailBecause "More recipes needed (#1268)"

-- | Known non-covered entities that need a recipe.
nonCoveredList :: [EntityName]
nonCoveredList =
  map
    T.toCaseFold
    [ "ash"
    , "blueprint"
    , "decoder ring"
    , "linotype"
    , "tape drive"
    , "wedge"
    ]

getNonCoveredEntities :: RG.RecipeGraph -> Set.Set Entity
getNonCoveredEntities graphData = RG.allEntities graphData `Set.difference` Set.unions (RG.levels graphData)