packages feed

camfort-0.905: tests/Camfort/Specification/Units/Analysis/ConsistentSpec.hs

-- TODO: Fix this
module Camfort.Specification.Units.Analysis.ConsistentSpec (spec) where

import System.FilePath ((</>))

import Control.Lens

import Test.Hspec hiding (Spec)
import qualified Test.Hspec as Test

import Language.Fortran.Util.ModFile (ModFile, emptyModFiles)

import Camfort.Analysis hiding (describe)
import Camfort.Analysis.ModFile (genModFiles)
import Camfort.Input (readParseSrcDir)
import Camfort.Specification.Units.Analysis (compileUnits)
import Camfort.Specification.Units.Analysis.Consistent (checkUnits)
import Camfort.Specification.Units.Monad
  (LiteralsOpt(..), unitOpts0, uoLiterals, runUnitAnalysis, UnitEnv(..))

spec :: Test.Spec
spec =
  describe "consistency analysis" $ do
    it "reports (simple) inconsistent units" $
       "example-inconsist-1.f90" `unitsCheckReportIs` exampleInconsist1CheckReport
    it "Polymorphic non-zero literal is not OK" $
       "inconsistLitInPolyFun.f90" `unitsCheckReportIs` inconsistLitInPolyFunReport
    it "Recursive Multiplication is not OK" $
       "inconsistRecMult.f90" `unitsCheckReportIs` inconsistRecMultReport
    describe "reports with varying Literal Modes" $ do
      it "LitMixed" $
        unitsCheckReportNoMod LitMixed    "inconsist3.f90" inconsist3LitMixedReport
      it "LitPoly" $
        unitsCheckReportNoMod LitPoly     "inconsist3.f90" inconsist3LitPolyReport
      it "LitUnitless" $
        unitsCheckReportNoMod LitUnitless "inconsist3.f90" inconsist3LitUnitlessReport
    describe "cross-module" $
      it "basic inconsistent" $
        unitsCheckReportWithMod ["cross-module-a/crossmoduleprovider.f90"] "cross-module-a/crossmoduleuser.f90"
          crossModuleInconsistBasicReport
    describe "literals" $ do
      it "nonzero literal with explicitly annotated polymorphic units-variable" $
        "literal-nonzero-inconsist1.f90" `unitsCheckReportIs` literalNonZeroInconsist1Report
      it "nonzero literal is unitless in poly-context" $
        "literal-nonzero-inconsist2.f90" `unitsCheckReportIs` literalNonZeroInconsist2Report
      it "monomorphism restriction is important (do-loop)" $
        "literal-nonzero-inconsist3.f90" `unitsCheckReportIs` literalNonZeroInconsist3Report
      -- it "monomorphism restriction is important (do-loop with zero-start)" $
      --   "literal-nonzero-inconsist4.f90" `unitsCheckReportIs` literalNonZeroInconsist4Report
      it "monomorphism restriction is important (goto)" $
        "literal-nonzero-inconsist5.f90" `unitsCheckReportIs` literalNonZeroInconsist5Report


fixturesDir :: String
fixturesDir = "tests" </> "fixtures" </> "Specification" </> "Units"

-- | Assert that the report of performing units checking on a file is as expected.
unitsCheckReport :: LiteralsOpt -> [String] -> String -> String -> Expectation
unitsCheckReport lo modNames fileName expectedReport = do
  let file = fixturesDir </> fileName
      modPaths = fmap (fixturesDir </>) modNames
  modFiles <- mapM mkTestModFile modPaths
  [(pf,_)] <- readParseSrcDir modFiles file []

  let uEnv = UnitEnv { unitOpts = uOpts, unitProgramFile = pf }

  report <- runAnalysisT file (logOutputNone True) LogError modFiles $ runUnitAnalysis uEnv $ checkUnits
  let res = report ^?! arResult . _ARSuccess

  show res `shouldBe` expectedReport
  where uOpts = unitOpts0 { uoLiterals = lo }

unitsCheckReportWithMod :: [String] -> String -> String -> Expectation
unitsCheckReportWithMod = unitsCheckReport LitMixed

unitsCheckReportNoMod :: LiteralsOpt -> String -> String -> Expectation
unitsCheckReportNoMod lo = unitsCheckReport lo []

-- | Assert that the report of performing units checking on a file is as expected.
unitsCheckReportIs :: String -> String -> Expectation
unitsCheckReportIs = unitsCheckReport LitMixed []

-- | Helper for producing a basic ModFile from a (terminal) module file.
mkTestModFile :: String -> IO ModFile
mkTestModFile file = head <$> genModFiles emptyModFiles compileUnits unitOpts0 file []

exampleInconsist1CheckReport :: String
exampleInconsist1CheckReport =
  "\ntests/fixtures/Specification/Units/example-inconsist-1.f90: Inconsistent:\n\
  \ - at 7:7: Units 's' and 'm' should be equal\n"

inconsist3LitMixedReport :: String
inconsist3LitMixedReport = inconsist3LitPolyReport

inconsist3LitPolyReport :: String
inconsist3LitPolyReport =
  "\ntests/fixtures/Specification/Units/inconsist3.f90: Inconsistent:\n\
   \ - at 6:3: 'j**2' should have the same units as 'k'\n\
   \ - at 7:7: 'k' should have unit 'a'\n\
   \ - at 8:3: 'j' should have unit '1'\n"

inconsist3LitUnitlessReport :: String
inconsist3LitUnitlessReport =
  "\ntests/fixtures/Specification/Units/inconsist3.f90: Inconsistent:\n\
  \ - at 5:3: 'j' should have unit '1'\n\
  \ - at 6:3: 'j**2' should have the same units as 'k'\n\
  \ - at 7:7: 'k' should have unit 'a'\n"

inconsistLitInPolyFunReport :: String
inconsistLitInPolyFunReport =
  "\ntests/fixtures/Specification/Units/inconsistLitInPolyFun.f90: Inconsistent:\n\
  \ - at 10:11: 'parameter 1 to sqr' should have unit 'm'\n\
  \ - at 15:13: 'z' should have unit '1'\n\
  \ - at 16:11: '(parameter 1 to sqr)**2' should have the same units as 'z'\n"

inconsistRecMultReport :: String
inconsistRecMultReport =
  "\ntests/fixtures/Specification/Units/inconsistRecMult.f90: Inconsistent:\n\
  \'parameter 2 to recur' should have the same units as 'parameter 2 to recur'\n\
  \'result of recur * parameter 2 to recur' should have the same units as 'result of recur'\n\
  \ - at 4:15: 'parameter 2 to recur' should have unit 'm'\n\
  \ - at 10:8: 'parameter 2 to recur' should have the same units as 'result of recur'\n"

crossModuleInconsistBasicReport :: String
crossModuleInconsistBasicReport =
  "\ntests/fixtures/Specification/Units/cross-module-a/crossmoduleuser.f90: Inconsistent:\n\
  \'add' should have the same units as 'parameter 1 to add'\n\
  \'add' should have the same units as 'parameter 2 to add'\n\
  \ - at 9:11: 'parameter 1 to add' should have unit 'm'\n\
  \ - at 9:14: 'parameter 2 to add' should have unit 's'\n"

literalNonZeroInconsist1Report :: String
literalNonZeroInconsist1Report =
  "\ntests/fixtures/Specification/Units/literal-nonzero-inconsist1.f90: Inconsistent:\n\
  \ - at 11:5: 'f_'a' should have unit '1'\n"

literalNonZeroInconsist2Report :: String
literalNonZeroInconsist2Report =
  "\ntests/fixtures/Specification/Units/literal-nonzero-inconsist2.f90: Inconsistent:\n\
  \ - at 5:9: 'parameter 1 to f' should have unit 'm'\n\
  \ - at 10:5: 'parameter 1 to f' should have unit '1'\n"

literalNonZeroInconsist3Report :: String
literalNonZeroInconsist3Report =
  "\ntests/fixtures/Specification/Units/literal-nonzero-inconsist3.f90: Inconsistent:\n\
  \ - at 7:11: 'parameter 1 to sqr' should have unit 's'\n\
  \ - at 12:3: 'i' should have the same units as 'parameter 1 to sqr'\n\
  \ - at 15:8: 'i' should have unit '1'\n"

literalNonZeroInconsist4Report :: String -- fixme
literalNonZeroInconsist4Report =
  "\ntests/fixtures/Specification/Units/literal-nonzero-inconsist5.f90: Inconsistent:\n\
  \- at 7:11: 'parameter 1 to sqr' should have unit 's'\n\
  \- at 15:9: 'i' should have the same units as 'parameter 1 to sqr'\n\
  \- at 17:12: 'i' should have unit '1'\n"

literalNonZeroInconsist5Report :: String
literalNonZeroInconsist5Report =
  "\ntests/fixtures/Specification/Units/literal-nonzero-inconsist5.f90: Inconsistent:\n\
  \ - at 7:11: 'parameter 1 to sqr' should have unit 's'\n\
  \ - at 13:23: 'j' should have unit '1'\n\
  \ - at 15:9: 'i' should have the same units as 'parameter 1 to sqr'\n\
  \ - at 17:12: 'j' should have the same units as 'i'\n"