Cabal's detailed test interface

Greetings interested Cabal users and developers! I am pleased to present another revision of Cabal's detailed test interface for your scrutiny. I have attached a patch for the Cabal repository to this message so you can try the new interface and test runner for yourselves, but the interface from Distribution.TestSuite is included below with a few of my comments. This interface is based very heavily on Duncan's proposal from our last round of discussion. I indicated deviations by comments in the source, but the commit log is a good summary of the changes: * The constructors of 'Tests' have been renamed. I think 'TestGroup' and 'ExtraTestOptions' are redundant: it is clear from context what sort of Group and what sort of ExtraOptions are being considered and qualified imports can resolve any name conflicts. Group and ExtraOptions have the advantage of improving the brevity of pattern matches on Tests, which are used very often in D.S.Test. * The 'concurrentSafe :: Bool' field of TestInstance has become the 'concurrently' field of Group, allowing package and test framework authors greater control over concurrency. * The 'Finished' constructor of 'Progress' now contains the options used to run the test in addition to the test result. Without returning the options, it would be difficult to extract the RNG seed used to run a test. * A detailed test suite module now exports the symbol 'test :: IO Tests'. This enables the use of IO to enumerate the tests in a group, suggested by Duncan as a way to accommodate the GHC test suite.
module Distribution.TestSuite ( TestInstance(..) , OptionDescr(..) , OptionType(..) , Tests(..) , Options , Progress(..) , Result(..) ) where
data TestInstance = TestInstance { run :: IO Progress -- ^ Perform the test. , name :: String -- ^ A name for the test, unique within a -- test suite. , tags :: [String] -- ^ Users can select groups of tests by -- their tags. , options :: [OptionDescr] -- ^ Descriptions of the options recognized -- by this test. , setOption :: String -> String -> Either String TestInstance -- ^ Try to set the named option to the given value. Returns an error -- message if the option is not supported or the value could not be -- correctly parsed; otherwise, a 'TestInstance' with the option set to -- the given value is returned. }
data OptionDescr = OptionDescr { optionName :: String , optionDescription :: String -- ^ A human-readable description of the -- option to guide the user setting it. , optionType :: OptionType , optionDefault :: Maybe String } deriving (Eq, Read, Show)
data OptionType = OptionFile { optionFileMustExist :: Bool , optionFileIsDir :: Bool , optionFileExtensions :: [String] } | OptionString { optionStringMultiline :: Bool , optionStringDescription :: String } | OptionNumber { optionNumberIsInt :: Bool , optionNumberBounds :: (Maybe String, Maybe String) } | OptionBool | OptionEnum [String] | OptionSet [String] | OptionRngSeed deriving (Eq, Read, Show)
data Tests = Test TestInstance | Group { groupName :: String , concurrently :: Bool -- ^ If true, then the child 'Test's or -- 'Group's can safely be run concurrently; -- otherwise, they must be run in series. , groupTests :: [Tests] } | ExtraOptions [OptionDescr] Tests
type Options = [(String, String)]
data Progress = Finished Options Result -- ^ The options used must be returned when the test is finished, -- or it will not be possible to recreate tests taking a random -- seed. | Progress String (IO Progress)
data Result = Pass | Fail String | Error String deriving (Eq, Read, Show)
As always, your comments are appreciated. Thanks! -- Thomas Tuegel

Curious about how a user of this API would look like I converted the parts of the test-framework API I use (for QC2 tests) to use the Cabal API. -- Converting a QC2 property to a Cabal test type. type TestName = String testProperty :: Testable a => TestName -> a -> Tests testProperty prop name = Test $ TestInstance { run = quickCheck ... , name = name , tags = [] , options = [] , setOption = \ _opt _val -> Left "No such option" } -- A lists of 'Tests' feels a bit unnatural, as "Tests" is already -- plural. testGroup :: TestName -> [Tests] -> Tests testGroup name = Group name True -- The actual tests prop_reverse :: Testable a prop_sort :: Testable a -- Always having to use a group feels a bit cumbersome (coming up with -- names for things is hard!). Perhaps the top-level export should be -- a list? tests = testGroup "Data.List" [ testProperty "reverse/reverse" prop_reverse , testProperty "sort" prop_sort ] A few notes: * Calling the data type that contains the Test, Group, and ExtraOptions constructor Tests feels a bit weird, as you often end up having to give a list of these e.g. to the Group constructor. You end up with something of type [Tests]. I think Test is a better name for this type. * Having the top-level 'tests' binding be IO Tests, instead of IO [Tests], is a bit inconvenient. It forces the users to always use at least one group, which forces him/her to name that group. As we all known, naming things is one of the two hard problems in computer science (the other being cache coherency). * I'm a bit confused about how Progress would be used in practice. It returns a String. What is the client supposed to do with this string? Returning a String doesn't seem enough to implement some kind of update-in-place cursors interface. * Perhaps we should expose some convince functions to create Test values. In particular I feel like you'd rarely want to create a TestInstance value without wrapping it in a Test constructor. I like the testGroup function exported by test-framework. Cheers, Johan

On Wed, Jul 27, 2011 at 5:06 AM, Johan Tibell
A few notes:
* Calling the data type that contains the Test, Group, and ExtraOptions constructor Tests feels a bit weird, as you often end up having to give a list of these e.g. to the Group constructor. You end up with something of type [Tests]. I think Test is a better name for this type.
It's true; I _do_ feel weird having something with the type [Tests]. I will change it.
* Having the top-level 'tests' binding be IO Tests, instead of IO [Tests], is a bit inconvenient. It forces the users to always use at least one group, which forces him/her to name that group. As we all known, naming things is one of the two hard problems in computer science (the other being cache coherency).
If we do this, should test runners assume that the list of tests can safely be run concurrently, or not? My primary motivation for requiring the user to use a group was the explicit answer to this question.
* I'm a bit confused about how Progress would be used in practice. It returns a String. What is the client supposed to do with this string? Returning a String doesn't seem enough to implement some kind of update-in-place cursors interface.
The String is intended to be a status message, something to display to the user. For comparison, the intermediate types used by test-framework are only required to have a Show instance, so I think this is sufficient.
* Perhaps we should expose some convince functions to create Test values. In particular I feel like you'd rarely want to create a TestInstance value without wrapping it in a Test constructor. I like the testGroup function exported by test-framework.
I will have a look at the convenience functions test-framework offers, and see which would be useful here. Thanks! -- Thomas Tuegel

On Fri, Jul 29, 2011 at 4:40 AM, Thomas Tuegel
On Wed, Jul 27, 2011 at 5:06 AM, Johan Tibell
wrote: * Having the top-level 'tests' binding be IO Tests, instead of IO [Tests], is a bit inconvenient. It forces the users to always use at least one group, which forces him/her to name that group. As we all known, naming things is one of the two hard problems in computer science (the other being cache coherency).
If we do this, should test runners assume that the list of tests can safely be run concurrently, or not? My primary motivation for requiring the user to use a group was the explicit answer to this question.
I think it's fine to assume that the tests can be run concurrently. This encourages users to make their tests concurrency safe. At work we have this default and even with our large code base I haven't seen any problems adopting such a policy. In fact, I think we should have a `testGroup` utility function that creates a concurrency safe group (by taking a single [Test] argument). Johan

On Tue, 2011-07-26 at 19:38 -0500, Thomas Tuegel wrote:
Greetings interested Cabal users and developers!
I am pleased to present another revision of Cabal's detailed test interface for your scrutiny.
First of all, thanks for going over this again. I think we're getting closer. Probably the next step, as Johan alludes to, is trying to implement this interface (e.g. a prototype adaptor for test-framework).
I have attached a patch for the Cabal repository to this message so you can try the new interface and test runner for yourselves, but the interface from Distribution.TestSuite is included below with a few of my comments.
This interface is based very heavily on Duncan's proposal from our last round of discussion. I indicated deviations by comments in the source, but the commit log is a good summary of the changes:
* The constructors of 'Tests' have been renamed. I think 'TestGroup' and 'ExtraTestOptions' are redundant: it is clear from context what sort of Group and what sort of ExtraOptions are being considered and qualified imports can resolve any name conflicts. Group and ExtraOptions have the advantage of improving the brevity of pattern matches on Tests, which are used very often in D.S.Test.
Having 'ExtraTestOptions' as another Tests constructor is a good idea.
* The 'concurrentSafe :: Bool' field of TestInstance has become the 'concurrently' field of Group, allowing package and test framework authors greater control over concurrency.
Yes, fine. That indicates better the scope of what it is that can be run concurrently. And in case it wasn't clear before, you and Max are right about how the distinction ought to be concurrency rather than purity. Q: what should 'concurrently' mean for a test group that contains other test groups? Suppose a test group asks for serial execution, does that mean it's ok to run tests in a sub-group in parallel with each other, so long as -- as a group -- they get run serially with respect to the top group?
* The 'Finished' constructor of 'Progress' now contains the options used to run the test in addition to the test result. Without returning the options, it would be difficult to extract the RNG seed used to run a test.
I'm not sure if I believe this one. Any test agent that cares about reproducing RNG seeds can just pick the RNG seed itself. It can tell what options are RNGs by looking for options with option type OptionRngSeed. Am I missing anything? On a related topic, I was looking back at an older conversation between you and Max. Max was criticising our GetOpt-style option parsing design of the time. He said: We are unlikely to do a good job at covering all use cases here. We could: 1. Accept that, and just stick with only providing --foo=bar arguments 2. Support more forms of arguments by using the GetOpt Option data type or our own implementation of it 3. Just delegate everything to the test provider by having them provide a (run :: p -> [String] -> IO (Either String Result)), where errors in the arguments are reported with Left "Error information". Optionally we could add a (usage :: IO String) function for retrieving usage information for the command line. I think I'm satisfied that our current design addresses these criticisms. We're now going more or less with option 3, in that the test provider is ultimately responsible for the parsing, and it just uses strings. But we're then augmenting that with a little bit of declarative presentation information so that test runners can provide reasonable user interfaces / forms for data input, while still leaving the data parsing / validation up to the test provider.
* A detailed test suite module now exports the symbol 'test :: IO Tests'.
Perhaps it should be tests, plural :-)
This enables the use of IO to enumerate the tests in a group, suggested by Duncan as a way to accommodate the GHC test suite.
Speaking of IO, do we ever need to deal with test suites that need imperative setup and teardown for sets of tests? Individual tests can do setup and teardown via the Progress which embeds IO. Does HUnit allow IO for sets/groups of tests? I'm just thinking of a recent little project we had at work where we were testing DB connections and had to populate a database with some sample data before running any of a group of tests. (Admittedly this was a performance rather than correctness test, but it almost certainly transfers to correctness tests.)
module Distribution.TestSuite ( TestInstance(..) , OptionDescr(..) , OptionType(..) , Tests(..) , Options , Progress(..) , Result(..) ) where
data TestInstance = TestInstance { run :: IO Progress -- ^ Perform the test. , name :: String -- ^ A name for the test, unique within a -- test suite. , tags :: [String] -- ^ Users can select groups of tests by -- their tags. , options :: [OptionDescr] -- ^ Descriptions of the options recognized -- by this test. , setOption :: String -> String -> Either String TestInstance -- ^ Try to set the named option to the given value. Returns an error -- message if the option is not supported or the value could not be -- correctly parsed; otherwise, a 'TestInstance' with the option set to -- the given value is returned. }
data OptionDescr = OptionDescr { optionName :: String , optionDescription :: String -- ^ A human-readable description of the -- option to guide the user setting it. , optionType :: OptionType , optionDefault :: Maybe String } deriving (Eq, Read, Show)
data OptionType = OptionFile { optionFileMustExist :: Bool , optionFileIsDir :: Bool , optionFileExtensions :: [String] } | OptionString { optionStringMultiline :: Bool , optionStringDescription :: String
What is this description? We've already got an option description. If it's an opaque string (as far as test runners are concerned) then it doesn't help them with presentation. I'd expect a hypothetical interactive GUI for a test suite to just validate string fields on the fly using setOption.
As always, your comments are appreciated. Thanks!
Ok, so if we're into the phase of validating our design, apart from seeing if we could adapt test-framework to fit this interface, we should also check on the output side that we have enough info / detail to be able to generate JUnit XML or whatever other is the format de jour for describing results of test suites. Perhaps one of us should have a go at seeing if we can make the ghc test suite fit this interface. Perhaps a project for the hackathon... Duncan

On Wed, Jul 27, 2011 at 7:32 PM, Duncan Coutts
Q: what should 'concurrently' mean for a test group that contains other test groups? Suppose a test group asks for serial execution, does that mean it's ok to run tests in a sub-group in parallel with each other, so long as -- as a group -- they get run serially with respect to the top group?
Yes, what you describe was my intent. I should document this more carefully in the module.
* The 'Finished' constructor of 'Progress' now contains the options used to run the test in addition to the test result. Without returning the options, it would be difficult to extract the RNG seed used to run a test.
I'm not sure if I believe this one. Any test agent that cares about reproducing RNG seeds can just pick the RNG seed itself. It can tell what options are RNGs by looking for options with option type OptionRngSeed.
You're probably right. I will remove the options from Finished for now while we validate the design. If it turns out to be necessary, we could easily re-add it before committing the final version of the interface.
* A detailed test suite module now exports the symbol 'test :: IO Tests'.
Perhaps it should be tests, plural :-)
It is, that's a typo in the patch log >_<
This enables the use of IO to enumerate the tests in a group, suggested by Duncan as a way to accommodate the GHC test suite.
Speaking of IO, do we ever need to deal with test suites that need imperative setup and teardown for sets of tests? Individual tests can do setup and teardown via the Progress which embeds IO. Does HUnit allow IO for sets/groups of tests?
I can see the utility of this. It would be simple enough to have Group take an (IO [Tests]) instead of just [Tests].
data OptionType = OptionFile { optionFileMustExist :: Bool , optionFileIsDir :: Bool , optionFileExtensions :: [String] } | OptionString { optionStringMultiline :: Bool , optionStringDescription :: String
What is this description? We've already got an option description.
You're right, of course; it serves no purpose. I think I added it in a moment of confusion.
As always, your comments are appreciated. Thanks!
Ok, so if we're into the phase of validating our design, apart from seeing if we could adapt test-framework to fit this interface, we should also check on the output side that we have enough info / detail to be able to generate JUnit XML or whatever other is the format de jour for describing results of test suites.
I'm looking at the JUnit schema right now. From the Dist.TestSuite side, it looks like we have all the information needed. However, it isn't possible to directly convert the logs made by "cabal test" to JUnit XML, as I previously claimed it was. The incompatibility is mainly due to a lack of granularity in capturing the standard output and error which was acceptable with the executable-stdio suites, but may not be acceptable here. This will require me to think about overhauling the default test runner in Dist.Simple.Test. -- Thomas Tuegel

On Thu, 2011-07-28 at 21:25 -0500, Thomas Tuegel wrote:
On Wed, Jul 27, 2011 at 7:32 PM, Duncan Coutts
wrote:
Speaking of IO, do we ever need to deal with test suites that need imperative setup and teardown for sets of tests? Individual tests can do setup and teardown via the Progress which embeds IO. Does HUnit allow IO for sets/groups of tests?
I can see the utility of this. It would be simple enough to have Group take an (IO [Tests]) instead of just [Tests].
Note that that's not quite the same thing. That lets you do IO to enumerate the tests, not IO before and after running a group of tests. I'm slightly confused about what you and Johan are talking about with [Tests] etc. Can you post what the new proposed data types are please? As for convenience functions, I don't know if it's worth it. We're not expecting packages to implement this directly. It'll just be for packages like test-framework to implement. But if they're cheap and obvious then I don't object strongly. Duncan

On Mon, Aug 8, 2011 at 5:52 PM, Duncan Coutts
On Thu, 2011-07-28 at 21:25 -0500, Thomas Tuegel wrote:
I can see the utility of this. It would be simple enough to have Group take an (IO [Tests]) instead of just [Tests].
Note that that's not quite the same thing. That lets you do IO to enumerate the tests, not IO before and after running a group of tests.
I'm slightly confused about what you and Johan are talking about with [Tests] etc. Can you post what the new proposed data types are please?
Johan and I are talking about renaming the Tests type to just "Test" to be more consistent about pluralization. So, also incorporating the changes I think you are suggesting to the Group type, it would look like: data Test = Test TestInstance | Group { groupName :: String , concurrently :: Bool , groupTests :: [Test] , preGroup :: IO () , postGroup :: IO () } | ExtraOptions [OptionDescr] Test Test modules would export: tests :: IO [Test]
As for convenience functions, I don't know if it's worth it. We're not expecting packages to implement this directly. It'll just be for packages like test-framework to implement. But if they're cheap and obvious then I don't object strongly.
The most important one I can think of would be testGroup :: String -> [Test] -> Test -- Make a Group with concurrently = True and no preGroup or postGroup I would expect test frameworks to provide interfaces that create a Test directly, rather than returning a TestInstance. I think that and the above function would cover all the common use cases. -- Thomas Tuegel

On Tue, 2011-08-16 at 14:23 -0500, Thomas Tuegel wrote:
Johan and I are talking about renaming the Tests type to just "Test" to be more consistent about pluralization. So, also incorporating the changes I think you are suggesting to the Group type, it would look like:
data Test = Test TestInstance | Group { groupName :: String , concurrently :: Bool , groupTests :: [Test] , preGroup :: IO () , postGroup :: IO () } | ExtraOptions [OptionDescr] Test
I think leave off the pre/post IO for now. It's not going to be really usable like that. It'd have to be something horrible like: data Test = Test (TestInstance a) | forall a. Group { groupName :: String , concurrently :: Bool , groupTests :: [Test a] , preGroup :: IO a , postGroup :: a -> IO () } data TestInstance a = TestInstance { run :: a -> IO Progress since the individual tests within a group need a handle to whatever was done in the pre/setup phase. For example imagine a testsuite for HDBC. Testing the backends requires creating a connection to a database, populating it with a bunch of test data, running one or more tests on that test db, and then cleaning and closing the db at the end of the tests. It's tricky because we want to enumerate all the tests without running setup code, but to run a test we'd have had to do all the setup and pass any results in to the tests. So I don't suggest we tackle this in full right now. We have a system with versioned test suite interfaces for a reason, and that's so we can change and add stuff later. Everything else seems fine. Lets try it out. Duncan

Hi all, I tried to use the detailed-1.0 test interface, according to the instructions on http://www.haskell.org/cabal/users-guide/#test-suites. What I got: bash-3.2$ cabal --version
cabal-install version 0.10.2 using version 1.10.2.0 of the Cabal library bash-3.2$ cabal configure cabal: HarmTrace.cabal:30: Test suite "test-harmtrace" is missing required field "type" or the field is not present in all conditional branches. The available test types are: exitcode-stdio-1.0
Snippet of the .cabal file: test-suite test-harmtrace
type: detailed-1.0 test-module: HarmTrace.Test.Test build-depends: HUnit >= 1.2, Cabal >= 1.10.2.0
What am I doing wrong? It seems like my cabal does not support anything other than exitcode... Thanks, Pedro

On Wed, 2011-08-31 at 09:30 +0200, José Pedro Magalhães wrote:
Hi all,
I tried to use the detailed-1.0 test interface, according to the instructions on http://www.haskell.org/cabal/users-guide/#test-suites.
Ah, a rare case of premature documentation. That is, documentation for a feature that is due, but not released yet. I'll update the docs to hide the mention of the detailed interface for the moment.
What am I doing wrong?
Nothing.
It seems like my cabal does not support anything other than exitcode...
That's right. No released version supports the detailed interface yet. Just recently we think we've settled on the final interface and will hopefully get that implemented in the darcs version soon. If you'd like to be guinea pig for that then I expect Thomas would like to talk to you. Duncan

Hi Duncan,
Is there a planned date for a release which will have the detailed test
interface?
Thanks,
Pedro
2011/9/5 Duncan Coutts
On Wed, 2011-08-31 at 09:30 +0200, José Pedro Magalhães wrote:
Hi all,
I tried to use the detailed-1.0 test interface, according to the instructions on http://www.haskell.org/cabal/users-guide/#test-suites.
Ah, a rare case of premature documentation. That is, documentation for a feature that is due, but not released yet. I'll update the docs to hide the mention of the detailed interface for the moment.
What am I doing wrong?
Nothing.
It seems like my cabal does not support anything other than exitcode...
That's right. No released version supports the detailed interface yet. Just recently we think we've settled on the final interface and will hopefully get that implemented in the darcs version soon. If you'd like to be guinea pig for that then I expect Thomas would like to talk to you.
Duncan

On Tue, 2011-09-06 at 11:16 +0200, José Pedro Magalhães wrote:
Hi Duncan,
Is there a planned date for a release which will have the detailed test interface?
My hope is that it'll be ready in time to be included in a release of Cabal to go along with GHC 7.4 towards the end of the year. If you'd like to help move things along then you could help us try out the new proposed interface. Thomas posted the latest patches yesterday. Duncan

Hi,
2011/9/9 Duncan Coutts
On Tue, 2011-09-06 at 11:16 +0200, José Pedro Magalhães wrote:
Hi Duncan,
Is there a planned date for a release which will have the detailed test interface?
My hope is that it'll be ready in time to be included in a release of Cabal to go along with GHC 7.4 towards the end of the year.
If you'd like to help move things along then you could help us try out the new proposed interface. Thomas posted the latest patches yesterday.
Ok, I''ll try to do that after I return from Tokyo. Pedro
Duncan
_______________________________________________ cabal-devel mailing list cabal-devel@haskell.org http://www.haskell.org/mailman/listinfo/cabal-devel

Greetings once again!
I have attached an updated patch with the requested changes to the
test interface. (I apologize for the delay; it was caused by the
unfortunate conspiracy of moving, not having internet access, and
qualifying exams. All of these being done, I will be more responsive
on Cabal-related things.)
Here is a summary of the patches, with some commentary:
This is the patch from before:
Tue Jul 26 17:29:23 CDT 2011 Thomas Tuegel
Perhaps one of us should have a go at seeing if we can make the ghc test suite fit this interface. Perhaps a project for the hackathon...
I had a look at the GHC test suite. It looks to me like the Python test runner would have to be replaced with some Haskell code to make it fit the detailed interface. I don't have any experience with GHC or its test suite, though; there may be an easier way that I'm not seeing. -- Thomas Tuegel

On 7 September 2011 20:14, Thomas Tuegel
Greetings once again!
I have attached an updated patch with the requested changes to the test interface. (I apologize for the delay; it was caused by the unfortunate conspiracy of moving, not having internet access, and qualifying exams. All of these being done, I will be more responsive on Cabal-related things.)
Here is a summary of the patches, with some commentary:
I just noticed I'd not applied these yet. Sorry for the delay. It looks good, now applied. Duncan
participants (4)
-
Duncan Coutts
-
Johan Tibell
-
José Pedro Magalhães
-
Thomas Tuegel