-
-
Notifications
You must be signed in to change notification settings - Fork 38
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
RFC006: proposal on unit-testing and property-based testing for TLA+ #741
Conversation
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.
It's great to see this! I'll do another pass later, but here are some initial comments. I really like the idea of apalache-mc example
!!
\* restrict the contents with TypeOK, | ||
\* so we don't generate useless data structures | ||
/\ TypeOK |
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.
Would it make sense to have a way to define generators that are useful and then invoke them here?
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.
Can you elaborate? Do you want to have generators in TypeOK
?
\* Note that succ(n) is not referring to state variables, | ||
\* so we can test it in isolation. | ||
\* | ||
\* @testStateless |
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.
Could we go with @test
instead of @testStateless
? Or do we predict that @test
could be used in the future for something else?
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.
I don't know. I think this is actually a rare case, when we have a stateless operator. When I was writing this text, it was not even clear to me, why cannot we directly tag succ
with @require
and @ensure
.
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.
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.
It's reasonable to expect that people would write larger specs like this. So far, I have not seen it often. It may also be a difference between people who come from imperative and functional languages.
(* | ||
* A test setup for ChangRobertsTyped. | ||
*) | ||
EXTENDS Integers, Apalache |
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.
Why do we need to import Apalache
?
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.
We are using Gen
, which is defined in Apalache
. If you are not using Gen
, you would not need it.
Co-authored-by: Vitor Enes <vitorenesduarte@gmail.com>
Co-authored-by: Vitor Enes <vitorenesduarte@gmail.com>
Co-authored-by: Vitor Enes <vitorenesduarte@gmail.com>
I have added a section on testing executions |
test/tla/ChangRobertsTyped_Test.tla
Outdated
(*************************** EXECUTION TESTS **********************************) | ||
\* Execute a sequence of 5 actions, similar to TestAction_n0. | ||
\* We test a final state with Assert_n0. | ||
\* Additionally, every state in an execution is tested for Correctness. | ||
\* | ||
\* @require("ConstInit") | ||
\* @require("Prepare_n0") | ||
\* @invariant("Correctness") | ||
\* @ensure("Assert_noWinner") | ||
\* @testExecution(5) | ||
TestExec_n0_n1 == | ||
\* in this test, we only execute actions by processes 1 and 2 | ||
\E self \in { 1, 2 }: | ||
n0(self) \/ n1(self) |
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.
I really like this idea!
I wonder if it would be possible to somehow write the following:
IfExec(trace) ==
/\ trace[0].action.name = "create_client"
/\ trace[0].action.id = 1
/\ trace[0].action.height = 3
/\ trace[1].action.name = "update_client"
/\ trace[1].action.id = 1
/\ trace[1].action.height = 2
ThenExec(trace) ==
/\ trace[0].outcome = "ok"
/\ trace[1].outcome = "invalid_height"
\* @require("IfExec")
\* @ensure("ThenExec")
\* @testExecution(2, "Init", "Next")
\* the execution doesn't have to start at `Init`;
\* instead, the execution can start from any state reachable from `Init` by applying `Next`
I don't think I'm using @require
and @ensure
correctly (i.e. as in the other examples), but I believe that it conveys the idea.
If something like this was supported, it could probably also be used to generate MBT tests in a much nicer way.
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.
Do you like to refer to the intermediate states in a trace? The idea was the you can talk about the variables of the first state and the last state. Otherwise, we are quickly approaching temporal logic :)
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.
@andrey-kuprianov what is your take on that? How far is it from the tests you need in MBT?
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.
Do you like to refer to the intermediate states in a trace?
Yes. My example is bad in that sense. I should have used a trace with more than 2 states.
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.
I think @shonfeder and you are asking about more or less the same thing. I don't like the idea of having trace
explicitly. I guess what you are asking for is a temporal property. Instead of giving an explicit index, we could write A /\ []B /\ <>C
. This would be the syntax for advanced users :-)
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.
Related: Trace
operator in CommunityModules https://github.com/tlaplus/CommunityModules/blob/4337afb74653cedcc2a24107759bf1613812db34/modules/TLCExt.tla#L42-L56
Also seems related to state- and action-constraints.
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.
Yep. I don't like the idea of exposing the trace explicitly. Once you do that, the users will write arbitrary code involving traces, which makes it much harder to analyze for symbolic techniques.
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.
I am also not sure what to do about the primed and unprimed variables in this test. It would be nice to make it less of a hack.
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.
Yep. I don't like the idea of exposing the trace explicitly. Once you do that, the users will write arbitrary code involving traces, which makes it much harder to analyze for symbolic techniques.
Power to the People tools. ✊
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.
Exciting stuff! Our discussion with @istoilkovska today seems to really confirm the utility of this stuff.
I posed some questions, just to help us consider different angles.
test/tla/ChangRobertsTyped_Test.tla
Outdated
\* @require("ConstInit") | ||
\* @require("Prepare_n0") | ||
\* @ensure("Assert_n0") | ||
\* @testAction | ||
TestAction_n0 == | ||
\E self \in Node: | ||
n0(self) |
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.
Is it impossible to express this effectively without the require
and ensure
annotations? E.g., is it impossible for this to be expressed as something like?
TestAction_n0 ==
/\ Require(ConstInit)
/\ Require(Prepare_n0)
/\ \E self \in Node:
n0(self)
/\ Ensure(Assert_n0)
I ask because, imo, it's generally not great when a parallel system of annotations starts to emerge, and preferable to express things within the language as much as possible. Of course, if it's really essential, then worth pursuing.
If it is essential, we should maybe work out a coherent syntax for the annotations. E.g., the annotations here don't really fit with the type
annotations: here we're missing a trailing ;
and we use a quoted argument, rather than a colon an unquoted term.
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.
If it is essential, we should maybe work out a coherent syntax for the annotations. E.g., the annotations here don't really fit with the type annotations: here we're missing a trailing ; and we use a quoted argument, rather than a colon an unquoted term.
We have two kinds of syntax for single-argument string annotations: https://apalache.informal.systems/docs/adr/004adr-annotations.html
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.
I ask because, imo, it's generally not great when a parallel system of annotations starts to emerge, and preferable to express things within the language as much as possible. Of course, if it's really essential, then worth pursuing.
I know what you mean. Several times we tried to express a DSL in TLA+ itself, and it happened to be a disaster. I think there are two reasons for that: (1) TLA+ is not an imperative language, (2) formulas have a very flexible structure, so it is tempting to combine things in unexpected ways.
Another reason for having @require
and @ensure
in annotations is that you can see them like that in contracts for verification tools, e.g., see Dafny, Ivy, VeriFast. So I am just copying what works well :-)
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.
I see! Thanks for explaining. This seems well motivated to me.
I'd be in favor of adding a section to the RFC to record the motivation for putting this in annotations instead of as a DSL.
\* @require("Prepare_n0") | ||
\* @invariant("Correctness") | ||
\* @ensure("Assert_noWinner") | ||
\* @testExecution(5) |
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.
again, I think we can probably settle on either having to quote args or not? If ConstInit
is an operator name, then it shouldn't be a string, right? Or else, what happens when we want to supply a string to one of these annotations?
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.
True, annotations are another layer. Some languages support contracts right in the language, e.g., Scala has requires
as part of the language. Maybe one day TLA+ will have it too, but for the moment annotations would work too.
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.
I think this reply may have meant for a different comment? (But I am convinced :))
test/tla/ChangRobertsTyped_Test.tla
Outdated
\* | ||
\* @require("ConstInit") | ||
\* @require("Prepare_n0") | ||
\* @invariant("Correctness") |
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.
With the invariants in particular, couldn't this just be in the body of the operator as well?
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.
I will add a discussion on that in the document. The problem is that all of these annotations can be written directly in TLA by writing temporal formulas. However, this often leads to confusion. Moreover, people start writing very powerful formulas right away. What we need in a test: (1) How to initialize the system, (2) what to execute, (3) what to check. Decomposing a formula into these three components is surprisingly hard, unless people are following a very rigid syntax, which nobody wants to follow.
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.
This is a good reason. Let's do record these motivations, along with #741 (comment) into the RFC>
\* @testExecution(5) | ||
TestExec_n0_n1 == | ||
\* in this test, we only execute actions by processes 1 and 2 | ||
\E self \in { 1, 2 }: |
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.
Here I'm wondering why we are not using Gen
or aren't putting this constraint in a Prepare
prerequisite. Not that those would be better, but it seems to me that we have like 3 different ways of accomplishing the same thing? If that's right, I think it'd be good to clarify which way is the preferred one and why. If not, it might help (at least a noob like me) to provide a paragraph justifying why we need these three different ways of stating constraints.
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.
Gen
is a bit of a hack for testing. I just realized that it is not explained in the document at all.
[Property-based testing]: https://en.wikipedia.org/wiki/QuickCheck | ||
[TLA+ examples]: https://github.com/tlaplus/Examples/ | ||
[LCR]: https://github.com/tlaplus/Examples/tree/master/specifications/chang_roberts | ||
[ChangRobertsTyped_Test.tla]: ../../../test/tla/ChangRobertsTyped_Test.tla |
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.
I think this should be a link to the file hosted by github, otherwise the link will be broken when this is served from the generated site.
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.
Good point. I will have to update the link, once this PR is merged.
Co-authored-by: Shon Feder <shon@informal.systems>
I have pushed more explanations. The discussions section is not finished yet. Also, added a pdf version of the file, so it is easy to read |
Added discussions on the issues raised by @shonfeder and I guess also by @vitorenesduarte |
|
||
In the rest of this section, we comment on the alternative approaches. | ||
|
||
#### 3.5.1. But I can do all of that in TLA+ |
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.
These sections make the particular feature set and approach very well motivated, IMO. Thanks for adding them!
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.
I was actually surprised how hard it was to express such a simple test in a dedicated operator, instead of creating a test in a separate file.
@andrey-kuprianov do you like to comment on this RFC? My plan was to post it on reddit, to see if TLA+ users would like to see a testing framework like that. |
@konnov you'll probably have better luck emailing the google group, I have not found many TLA+ people on Reddit |
@Isaac-DeFrain, yeah, the tla mailing list too. I just want to do it in stages, so we don't get a storm of replies :) |
Let's continue the discussion in #817 |
This is an RFC to discuss unit testing and PBT for TLA+. Once we agreed on that internally, we should get the feedback from a wider audience.
Compiled version in pdf.
To see the code in the RFC, run the following:
Tests added for any new codeRanmake fmt-fix
(or had formatting run automatically on all files edited)