Skip to content

Commit

Permalink
Merge pull request #433 from smucclaw/le-tests
Browse files Browse the repository at this point in the history
Le tests
  • Loading branch information
joewatt95 authored Sep 19, 2023
2 parents f02dd42 + 56ce0c3 commit 871783a
Show file tree
Hide file tree
Showing 15 changed files with 843 additions and 0 deletions.
89 changes: 89 additions & 0 deletions lib/haskell/natural4/.golden/is-num/actual
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
the target language is: prolog.

the templates are:
*a var* is *a var*.
*a class*'s *a field* is *a value*,
*a class*'s nested *a list of fields* is *a value*,
*a class*'s *a field0*'s *a field1* is *a value*,
*a class*'s *a field0*'s *a field1*'s *a field2* is *a value*,
*a class*'s *a field0*'s *a field1*'s *a field2*'s *a field3* is *a value*,
*a class*'s *a field0*'s *a field1*'s *a field2*'s *a field3*'s *a field4* is *a value*.

*a number* is a lower bound of *a list*,
*a number* is an upper bound of *a list*,
*a number* is the minimum of *a number* and the maximum of *a number* and *a number*,
the sum of *a list* does not exceed the minimum of *a list*,
*a number* does not exceed the minimum of *a list*.

% Predefined stdlib for translating natural4 -> LE.
the knowledge base prelude includes:
% Note: LE's parsing of [H | T] is broken atm because it transforms that
% into [H, T] rather than the Prolog term [H | T].

% a class's nested [] is a value.

% a class's nested [a field | a fields] is a value
% if the class's the field is an other class
% and the other class's nested the fields is the value.

% Nested accessor predicates.
a class's a field0's a field1 is a value
if class's field0 is a class0
and class0's field1 is value.

a class's a field0's a field1's a field2 is a value
if class's field0 is a class0
and class0's field1 is a class1
and class1's field2 is value.

a class's a field0's a field1's a field2's a field3 is a value
if class's field0 is a class0
and class0's field1 is a class1
and class1's field2 is a class2
and class2's field3 is value.

a class's a field0's a field1's a field2's a field3's a field4 is a value
if the class's field0 is a class0
and class0's field1 is a class1
and class1's field2 is a class2
and class2's field3 is a class3
and class3's field4 is value.

% Arithmetic predicates.
a number is an upper bound of a list
if for all cases in which
a X is in list
it is the case that
X is [a class, a field]
and class's field is a value
and number >= value
or number >= X.

a number is a lower bound of a list
if for all cases in which
a X is in list
it is the case that
X is [a class, a field]
and class's field is a value
and number =< value
or number =< X.

% number = min(x, max(y, z))
a number is the minimum of a x and the maximum of a y and a z
if a m is the maximum of [y, z]
and number is the minimum of [x, m].

a number does not exceed the minimum of a list of numbers
if a min is the minimum of list of numbers
and number =< min.

the sum of a list does not exceed the minimum of a other list
if a x is the sum of list
and x does not exceed the minimum of other list.

the knowledge base encoding includes:
a total savings is 100
if a initial savings is 22.5.

query q is:
0 < 1.
89 changes: 89 additions & 0 deletions lib/haskell/natural4/.golden/is-num/golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
the target language is: prolog.

the templates are:
*a var* is *a var*.
*a class*'s *a field* is *a value*,
*a class*'s nested *a list of fields* is *a value*,
*a class*'s *a field0*'s *a field1* is *a value*,
*a class*'s *a field0*'s *a field1*'s *a field2* is *a value*,
*a class*'s *a field0*'s *a field1*'s *a field2*'s *a field3* is *a value*,
*a class*'s *a field0*'s *a field1*'s *a field2*'s *a field3*'s *a field4* is *a value*.

*a number* is a lower bound of *a list*,
*a number* is an upper bound of *a list*,
*a number* is the minimum of *a number* and the maximum of *a number* and *a number*,
the sum of *a list* does not exceed the minimum of *a list*,
*a number* does not exceed the minimum of *a list*.

% Predefined stdlib for translating natural4 -> LE.
the knowledge base prelude includes:
% Note: LE's parsing of [H | T] is broken atm because it transforms that
% into [H, T] rather than the Prolog term [H | T].

% a class's nested [] is a value.

% a class's nested [a field | a fields] is a value
% if the class's the field is an other class
% and the other class's nested the fields is the value.

% Nested accessor predicates.
a class's a field0's a field1 is a value
if class's field0 is a class0
and class0's field1 is value.

a class's a field0's a field1's a field2 is a value
if class's field0 is a class0
and class0's field1 is a class1
and class1's field2 is value.

a class's a field0's a field1's a field2's a field3 is a value
if class's field0 is a class0
and class0's field1 is a class1
and class1's field2 is a class2
and class2's field3 is value.

a class's a field0's a field1's a field2's a field3's a field4 is a value
if the class's field0 is a class0
and class0's field1 is a class1
and class1's field2 is a class2
and class2's field3 is a class3
and class3's field4 is value.

% Arithmetic predicates.
a number is an upper bound of a list
if for all cases in which
a X is in list
it is the case that
X is [a class, a field]
and class's field is a value
and number >= value
or number >= X.

a number is a lower bound of a list
if for all cases in which
a X is in list
it is the case that
X is [a class, a field]
and class's field is a value
and number =< value
or number =< X.

% number = min(x, max(y, z))
a number is the minimum of a x and the maximum of a y and a z
if a m is the maximum of [y, z]
and number is the minimum of [x, m].

a number does not exceed the minimum of a list of numbers
if a min is the minimum of list of numbers
and number =< min.

the sum of a list does not exceed the minimum of a other list
if a x is the sum of list
and x does not exceed the minimum of other list.

the knowledge base encoding includes:
a total savings is 100
if a initial savings is 22.5.

query q is:
0 < 1.
3 changes: 3 additions & 0 deletions lib/haskell/natural4/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ dependencies:
- optics
- generic-optics
- nonempty-containers
- safe
- lens-regex-pcre
- pcre-heavy
- string-conversions
Expand Down Expand Up @@ -116,6 +117,8 @@ tests:
- natural4
- filepath
- filemanip
- hspec-golden
- yaml

benchmarks:
natural4-bench:
Expand Down
19 changes: 19 additions & 0 deletions lib/haskell/natural4/test/LS/XPile/LogicalEnglish/GoldenUtils.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module LS.XPile.LogicalEnglish.GoldenUtils (goldenLE) where

import LS.XPile.LogicalEnglish.UtilsLEReplDev (leTestcasesDir)
import System.FilePath ((</>), takeBaseName)
import Test.Hspec.Golden (Golden (..))

goldenLE :: FilePath -> String -> Golden String
goldenLE testcase actualOutput =
Golden
{ output = actualOutput,
encodePretty = show,
writeToFile = writeFile,
readFromFile = readFile,
goldenFile = leTestcasesDir </> testcaseName </> "expected.le",
actualFile = Just $ leTestcasesDir </> testcaseName </> "actual.le",
failFirstTime = False
}
where
testcaseName = takeBaseName testcase
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{-# LANGUAGE BlockArguments #-}

-- |
-- Module: LogicalEnglishSpec
-- Description: Defines a hspec Spec for testing the Logical English transpiler.
--
-- This module is responsible for running unit tests for the Logical English
-- transpiler.
-- To define a test case, one can create a new directory in
-- leTestcasesDir, like 'is-num' with the following structure:
-- - {leTestcasesDir}
-- - is-num
-- - is-num.csv
-- - expected.le
-- - config.yml
-- where:
-- - 'is-num.csv' is the name of the input natural4 csv file.
-- - 'expected.le' is a file containing the expected Logical English output.
-- - 'condig.yml' is a YAML configuration file for the unit test.
--
-- Note that:
-- - 'config.yml' should look something like:
-- description: "If IS is followed by a number, number should become *a number*"
-- enabled: true
-- - ie it should contain a string field called 'description', which gives
-- the description of the test case found in that directory, and
-- 'enabled' is a boolean (ie 'true' or 'false') indicating if the test
-- case is currently enabled.
-- Test cases which are not enabled are skipped automatically by this module.
--
-- - The file names 'expected.le' and 'config.yml' are hardcoded
-- and so should not be varied
-- - The name of the parent directory and csv file can be changed, but should be
-- kept in sync with each other, ie if the parent directory is 'abc', then
-- the csv file should be called 'abc.csv'.
--
-- After creating a unit test, one can register it to be run by this module
-- by adding the name of the directory as an entry in
-- '{leTestcasesDir}/testcases.yml'.
-- Only those test cases which are listed in this file will be run.
--
-- When Logical English test cases are run via 'stack test', a new file called
-- 'actual.le' will appear in the respective directories of each test case
-- that was run.
-- This file represents the actual output of the Logical English transpiler.
module LS.XPile.LogicalEnglish.LogicalEnglishSpec (spec) where

import Control.Monad (join)
import Data.Foldable (for_)
import Flow ((|>))
import LS.Utils ((|$>))
import LS.XPile.LogicalEnglish.Testcase (configFile2spec)
import LS.XPile.LogicalEnglish.SpecUtils (findWithDepth0)
import LS.XPile.LogicalEnglish.UtilsLEReplDev (leTestcasesDir)
import Safe (tailSafe)
import System.FilePath ((</>))
import System.FilePath.Find (FileType (Directory), fileType, (==?))
import Test.Hspec (Spec, describe, runIO)

-- | The 'Spec' used to test the Logical English transpiler.
spec :: Spec
spec = describe "Logical English" do
directories :: [FilePath] <-
leTestcasesDir
|> findWithDepth0 (fileType ==? Directory)
-- The first directory will always be leTestcasesDir itself, which is why
-- we need to take the tail to get rid of it.
|$> tailSafe
|> runIO
for_ directories \directory ->
directory </> "config.yml"
|> configFile2spec |> runIO |> join
17 changes: 17 additions & 0 deletions lib/haskell/natural4/test/LS/XPile/LogicalEnglish/SpecUtils.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module LS.XPile.LogicalEnglish.SpecUtils
( findWithDepth0,
modifyError
)
where

import Control.Monad.Except
( runExceptT, MonadError(throwError), ExceptT )
import System.FilePath.Find (depth, (==?))
import System.FilePath.Find qualified as FileFind

findWithDepth0 :: FileFind.FilterPredicate -> FilePath -> IO [FilePath]
findWithDepth0 = FileFind.find (depth ==? 0)

-- | Backported from mtl-2.3.1
modifyError :: MonadError e' m => (e -> e') -> ExceptT e m a -> m a
modifyError f m = runExceptT m >>= either (throwError . f) pure
Loading

0 comments on commit 871783a

Please sign in to comment.