-
Notifications
You must be signed in to change notification settings - Fork 110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add exact dependency matching #343
Add exact dependency matching #343
Conversation
@Bodigrim @VictorCMiraldo I'm eager to get this PR merged, my aim is to fix all of the gotchas listed in Tasty's README regarding dependencies - these affect the main project I'm working on:
This PR together with a followup PR should achieve that. I know it's quite a bit to review, so I've tried to add enough tests to hopefully convince you that it's at least somewhat sound. If you'd like a video call to talk this through, I'm open for that too! If there's anything else I can do, ask away. |
@martijnbastiaan sorry, I don't have bandwidth for this right now. Could you possibly summarise all breaking changes here (both API and behaviour)? CC @VictorCMiraldo @adamgundry @andreasabel as other maintainers. |
No worries @Bodigrim, thanks for the reply. The API changes are as follows:
There are no behavioral changes to existing programs. |
Thanks for the PR @martijnbastiaan! That is a large one! :) |
Understood! Hmm. So to give some background: I wrote this patch to resolve some problems we face with our 4K large testsuite over at clash-lang/clash-compiler, a project me and my colleagues at QBayLogic maintain. What I could do is ask a colleague for a review on this patch (and the follow-up one). Would that be enough -together with the listed API changes I posted earlier- to accept this patch? I'm of course willing to maintain the feature too, and deal with the fallout if any. |
Can this breaking change be avoided by introducing a new fold and expressing existing foldSingle via it? |
Yes, we could add |
@martijnbastiaan could you please share what kind of dependency graph is typical for Clash test suite? Is it chains of depending tests? |
Sure. To give a bit of context: Clash is a Haskell to silicon compiler - meaning Haskell can be used to build chips that could be synthesized by say a TSMC (or more realistically; it's used to program a reconfigurable chip). Silicon is typically written in a Hardware Description Language (HDL) which is what Clash targets as a compiler output. There are currently three major HDLs: Verilog, SystemVerilog, and VHDL. Each of these languages have various simulators that can be used to predict chip behavior before actually burning it into silicon. Clash itself can also be simulate (simply by leveraging GHC!). So, one test looks something like: graph TD;
FooTest-->VHDL;
FooTest-->Verilog;
FooTest-->SystemVerilog;
VHDL-->Clash_VHDL;
VHDL-->Clash_Sim_VHDL;
Verilog-->Clash_Verilog;
Verilog-->Clash_Sim_Verilog;
SystemVerilog-->Clash_SV;
SystemVerilog-->Clash_Sim_SV;
Clash_SV-->ModelSim;
Clash_SV-->Verilator;
Clash_VHDL-->GHDL;
Clash_VHDL-->Vivado_VDHL;
Clash_Verilog-->Vivado_Verilog;
Clash_Verilog-->IcarusVerilog;
I'm glossing over some things, but each new (regression) test looks a bit like this. This makes the number of dependencies grow linearly with the number of tests, hence us running into this caveat mentioned in the README:
With regards to:
I'm willing to help out patching them, of course. |
I agree that the infrastructure for dependencies between tests is wanting, but I feel that your approach covers only a fraction of cases, but required changes are quite profound. My use case is dependencies between disjoint, potentially remote trees, see https://hackage.haskell.org/package/tasty-bench-0.3.2/candidate/docs/Test-Tasty-Bench.html#g:4 If the shape of your dependency graphs is relatively stable, might your use case be better served with a custom |
The only real limitation is that you can't make a DAG, you can only make a tree. I've checked out the example of bcompare and the examples it links ( |
Correct, none of my examples needs a DAG. However, the shape of |
Sure, that's true. I'll be honest, it's kinda hard to respond to this without sounding very negative/argumentative. It seems to me that you're paying a pretty high price for the ability to do so. The examples need to construct patterns such as: "$NF == \"" ++ show n ++ "\" && $(NF-1) == \"Chimera\"" ..which would break if there's ever another test group called So all in all, it seems somewhat "obvious" to me that you'd want something like So, in a way I feel that this remark:
turns everything on its head. It seems to me that I guess what I'm asking is: is there any chance of this getting merged? If not, do you see an alternative way of tackling the issues in the README, or will Tasty be forever stuck with them? |
@martijnbastiaan to be very clear, I agree that the dependency design is inconvenient and I also agree that if we were doing it from the scratch and with our current experience, then So, answering your question directly, yes, there is a chance, but we must consider the design and long-term consequences. This can take more time than one might like, sorry for that. I do appreciate your work, your efforts and this particular contribution. What is the prior art for dependencies between tests in other frameworks? Could you define something like data ClashTest = ClashTest
{ ghdlTest :: Assertion
, vivadoVdhlTest :: Assertion
, vivadoVerilogTest :: Maybe Assertion
, icarusVerilogTest :: [Assertion]
, ...
} and provide |
Understood. If necessary, I'd be willing to maintain it. My ideal would be to deprecate I'll have a look at your proposal and dig into the prior art, hopefully this weekend. |
I've looked into this:
To me it looks like sydtest is very close to doing TheRightThing(tm).
This can work, but it has quite a few drawbacks by virtue of the tests being opaque to Tasty:
So all in all this would be a pretty big step back IMO. I could do two things to make this series of patches more acceptable:
-- | An algebra for folding a `TestTree`.
--
-- Instead of constructing fresh records, build upon `trivialFold`
-- instead. This way your code won't break when new nodes/fields are
-- indroduced.
data TreeFold b = TreeFold
{ foldSingle :: forall t . IsTest t => OptionSet -> TestName -> t -> b
, foldGroup :: OptionSet -> TestName -> b -> b
, foldResource :: forall a . OptionSet -> ResourceSpec a -> (IO a -> b) -> b
, foldAfter :: OptionSet -> DependencyType -> Expr -> b -> b
-- "Exact" versions of functions defined above. In 'trivialFold', these simply
-- call the non-exact versions. These functions are here for backwards compatiblity
-- reasons.
--
-- Note: instead of taking a `TreeFold`, these functions could take their non-exact
-- function directly, but this makes the type signatures quite long.
, exactFoldSingle :: forall t . IsTest t => TreeFold b -> ExactPath -> OptionSet -> TestName -> t -> b
, exactFoldGroup :: TreeFold b -> ExactPath -> OptionSet -> TestName -> b -> b
, exactFoldResource :: forall a . TreeFold b -> ExactPath -> OptionSet -> ResourceSpec a -> (IO a -> b) -> b
, exactFoldAfter :: TreeFold b -> ExactPath -> OptionSet -> DependencyType -> Expr -> b -> b
} I personally don't think it's worth it, but I'm happy to do it if that makes things more acceptable/reviewable.
Thanks btw, happy to work with you guys. |
Would |
For my specific use case it would be both a step forward and backward at the same time. The positives:
The neutrals:
The negatives:
I'm somewhat biased as I already put in the work of course, but it strikes me as a band-aid instead of a proper solution. Do you think it would be easier to implement? |
Well, adding |
Sure, I can amend the patch to do this. |
@martijnbastiaan before you do more work, I'd like to hear from other maintainers. @VictorCMiraldo @andreasabel Existing Edited: |
@Bodigrim and @martijnbastiaan, first of all, thanks for the great work both making the PR and the thoughtful comments! :)
Yeah, that doesn't sound like a very good solution and I can see how running tests sequentially is an interesting feature. It does feel more natural to to add a Would a |
Potentially we can leave |
Fundamentally, The core of the problem is that this variable: Lines 327 to 332 in df7ccab
needs to take |
Hmm.. An alternative implementation could implement a custom folder that propagates dependencies up while it is folding. I.e., it would not use |
I would do this (please find a better name for data ExecutionOrder = Concurrent | Sequential
data TestTree
= forall t . IsTest t => SingleTest TestName t
| TestGroup' ExecutionOrder TestName [TestTree]
| PlusTestOptions (OptionSet -> OptionSet) TestTree
| forall a . WithResource (ResourceSpec a) (IO a -> TestTree)
| AskOptions (OptionSet -> TestTree)
| After DependencyType Expr TestTree
pattern TestGroup = TestGroup' Concurrent This is a breaking change, but a very minor one. And you can leave Indeed, |
FWIW, I like to compromise suggested by @Bodigrim above. Seems like a good option to minimize breakage while enabling @martijnbastiaan to have the necessary feature that motivated the PR in the first place. |
Great, thank you both. I've got a patch now that implements the suggestions (adding TestGroup field + custom traversal). While it doesn't try to fix any of the issues raised in the README for pattern dependencies, it does solve them for the new way of specifying deps in a fairly straightforward way. I'm pretty happy with the patch, but I'm still working on writing a proper testsuite. I'm spending only a few hours on it every weekend so it'll probably take a while.. |
962af17
to
4b67d34
Compare
4b67d34
to
48f4a4a
Compare
@Bodigrim All done :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@martijnbastiaan I'm truly sorry that it takes so long, I'm very much overloaded recently. This is hopefully the last round, unless @andreasabel wants to take a look.
48f4a4a
to
919aff3
Compare
No worries, I appreciate the feedback. If it would be appreciated: I could help out reviewing PRs for this project and doing some general maintenance. |
919aff3
to
3554c4f
Compare
Tasty 1.2 introduced a way for tests to specify dependencies. That is, what tests should run before themselves. These dependencies are specified using an AWK-like expression annotated to a `TestTree`. These expressions can match against any test in the full `TestTree`. This approach has a few rough edges: * Any pattern has to be tested against any test in the tree. If your test tree mostly consists of tests specifying dependencies for each other, this means calculating your test tree is of quadratic complexity. * It's easy to introduce dependency cycles, or other mistakes introducing needless sequentiality. The latter being especially insidious as it easily goes unnoticed. This commit introduces the ability to specify dependencies by using a combinator taking a list of `TestTree`s. This way of specifying dependencies removes the quadratic complexity in favor of a linear one, while eliminating the ability to accidentally introduce cycles or unintended sequentiality.
3554c4f
to
4d937e4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks! Unless there is any feedback from @andreasabel by the end of May, I'll proceed with a merge.
Thanks for persistence, @martijnbastiaan :)
|
@martijnbastiaan much appreciated! Sorry, I don't have admin rights to grant you formal privileges (and I'm reluctant to distract @UnkindPartition by asking), but you are most welcome. |
I read about his situation, best not to distract him yeah. I'll see what I can do for the listed issues/PRs. |
Tasty 1.2 introduced a way for tests to specify dependencies. That is,
what tests should run before themselves. These dependencies are
specified using an AWK-like expression annotated to a
TestTree
. Theseexpressions can match against any test in the full
TestTree
. Thisapproach has a few rough edges:
Any pattern has to be tested against any test in the tree. If your
test tree mostly consists of tests specifying dependencies for each
other, this means calculating your test tree is of quadratic
complexity.
It's easy to introduce dependency cycles, or other mistakes
introducing needless sequentiality. The latter being especially
insidious as it easily goes unnoticed.
This commit introduces the ability to specify dependencies by using a
combinator taking a list of
TestTree
s. This way of specifying dependenciesremoves the quadratic complexity in favor of a linear one, while
eliminating the ability to accidentally introduce cycles or unintended
sequentiality.
A bit of history for this PR as it has undergone several rewrites thanks
to a few great reviews I've gotten:
The PR originated from friction experienced over at
clash-lang/clash-compiler
where it was hard to define tests sequentially in a programmatic manner and
startup of the testsuite now takes between 30-60 seconds. See this comment.
The initial version added an
AfterTree TestTree TestTree
constructor, andwould build a unique name for each test in the tree. Using a
Trie
it wouldthen be able to relatively cheaply find dependencies of test cases. As a
bonus, it would solve 3 out of 4 issues documented in the README
for dependencies defined using patterns too. However, the implementation
complexity of this was deemed to high to be merged.
The current implementation does not try to fix any problems with the existing
way of defining dependencies, but instead focuses on just doing that for the
new way. The new way
being an extra field added tomarks test trees as sequential usingTestGroup
, which indicateswhether tests should run in parallel or sequentially. To implement this
properly, two backwards incompatible changes had to be made as documented
over here.
IsOption ExecutionMode
. Inorder to implement sequential execution
foldGroup
had its type signaturechanged from
foldGroup :: OptionSet -> TestName -> b -> b
tofoldGroup :: OptionSet -> TestName -> [b] -> b
, implemented inRefactor
createTestActions
#364.