-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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 try-commit to test assertions #1947
Conversation
Oh wow, this is very exciting @qlonik! I must admit I've kind of forgotten how I'm terribly sorry about that. I'm rather geeked about shipping this feature! |
Yea, it was a bit difficult to follow. There are functions being bound to instance of I was thinking that it would be cool to refactor assertions as a class with whatever fields they require, and |
I would like to rebase this branch on top of master. Anything I should worry about? |
Go for it. I’ve been terribly busy of late, hoping to have a look after next week. |
Its okay if it takes time. Its holidays after the next week anyway, or something :) I've been using snapshot of ava forced on commit that includes these changes. It worked for me quite well. It will be cool when it makes it into ava :) |
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.
Hey @qlonik sorry for the long wait. This looks pretty good! Some comments below.
Another thought. If we're limiting to one concurrent test('inline', async t => {
const result = await t.try(() => {
t.pass()
})
result.commit()
})
const reusable = t => t.pass()
test('reuse', async t => {
const result = await t.try(reusable)
result.commit()
}) We prefer the |
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.
Some of the changes were made keeping in my use case of property testing. Here is basically how I'm using ava in the current iteration - https://gist.github.com/qlonik/9a297285284d71f7da47022f120ef4ad.
From this use case, there might be hundreds of t.try()
runs inside the macro provided by the library. It is transparent to user. Whoever writes the test might need to debug the test using t.log
, and so the macro needs to be able to print those logged values, so I thought that logs should be returned from the attempt. Similarly with the title of the attempt - it is an easy way to keep track of the current attempt to which logs belong. Maybe instead of the title, we could return the current attempt number from attempt counter?
I was wondering if we should explicitly fail when t.try()
and commit()
/discard()
are not properly used. Should it be more explicit and throw errors when it is not properly used or should it be more implicit and allow some duplicate commit calls or discard after commit?
Do you have idea if there are some tests missing?
EDIT: Sorry for duplicate messages, I didnt know github would do that.
lib/test.js
Outdated
fn, | ||
title: this.title + '.A' + (this.attemptCount++) | ||
}); | ||
return new Test(opts); |
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 adjusting the title in order to prevent any conflicts related to snapshots. Isn't it the case that if we adjust the title, then there are no problems about number of concurrent t.try()
and both may contain snapshots?
lib/test.js
Outdated
@@ -213,6 +302,34 @@ class Test { | |||
this.saveFirstError(error); | |||
} | |||
|
|||
addPendingAttemptAssertion() { | |||
if (this.finishing) { | |||
this.saveFirstError(new Error('Assertion passed, but test has already finished')); |
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.
Should it be Attempt is complete, but the test has already finished
?
lib/test.js
Outdated
|
||
countFinishedAttemptAssertion(includeCount) { | ||
if (this.finishing) { | ||
this.saveFirstError(new Error('Assertion passed, but test has already finished')); |
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.
Should it be the same as above?
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.
Whoever writes the test might need to debug the test using
t.log
, and so the macro needs to be able to print those logged values, so I thought that logs should be returned from the attempt.
Oh good point! Perhaps attempt.discard({ retainLogs: true })
would include them in the test results?
Similarly with the title of the attempt - it is an easy way to keep track of the current attempt to which logs belong. Maybe instead of the title, we could return the current attempt number from attempt counter?
Yea we should include that in the logs, but perhaps also have a form of t.try('unique attempt title', fn)
?
I was wondering if we should explicitly fail when
t.try()
andcommit()
/discard()
are not properly used. Should it be more explicit and throw errors when it is not properly used or should it be more implicit and allow some duplicate commit calls or discard after commit?
Commit-after-commit and discard-after-discard should be no-ops. They're harmless. Commit-after-discard and discard-after-commit should fail I think.
As per below, I think we should remove the discard-while-running behavior for now. What do you think about not allowing concurrent attempts?
lib/test.js
Outdated
fn, | ||
title: this.title + '.A' + (this.attemptCount++) | ||
}); | ||
return new Test(opts); |
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.
The snapshots are indexed on the title, so if currently you only commit the third attempt, then later if the first attempt is committed you won't actually compare the snapshot. See also #1585.
lib/test.js
Outdated
@@ -213,6 +302,34 @@ class Test { | |||
this.saveFirstError(error); | |||
} | |||
|
|||
addPendingAttemptAssertion() { | |||
if (this.finishing) { | |||
this.saveFirstError(new Error('Assertion passed, but test has already finished')); |
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.
Not sure at the moment, sorry 😄
lib/test.js
Outdated
|
||
countFinishedAttemptAssertion(includeCount) { | ||
if (this.finishing) { | ||
this.saveFirstError(new Error('Assertion passed, but test has already finished')); |
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.
Not sure at the moment, sorry 😄
With regards to concurrent attempts. I think it would be nice to allow attempt to be concurrent. From my use case, I'm passing async function to the library, and I have no control whether that is called concurrently or not, so I might have to do some magic outside of passed function to ensure that attempts run sequentially. It might affect performance in some cases. Additionally, if we choose to do one test at a time or multiple tests at a time, switching to the other one will probably be a breaking change. Since people might rely on one or another behavior. Also, ava runs tests in parallel by default, so maybe attempts should also be run by default in parallel? I would probably prefer concurrent attempts, unless we figure out that something breaks if it runs concurrently. I'm still pretty confused with snapshots and test titles. It seems that if title of the attempt is changed, then the snapshot will be bound to that title. I'm not sure if it depends whether attempt is committed or discarded. The attempts should have the same title, unless the order of them is changed. Maybe this will be influenced by the fact there are concurrent attempts or not. Last thing that I'm hesitant to change is about setting EDIT: It might be nicer to have special mode for attempts via |
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.
Thanks for the fixes @qlonik. I think this is pretty close actually! Some comments below. Let me know if there's anything specific you'd prefer me to pick up.
I'm still pretty confused with snapshots and test titles. It seems that if title of the attempt is changed, then the snapshot will be bound to that title. I'm not sure if it depends whether attempt is committed or discarded. The attempts should have the same title, unless the order of them is changed. Maybe this will be influenced by the fact there are concurrent attempts or not.
Snapshots are indexed by test title, and then by order of invocation. E.g. if you have this test:
test('ordered snapshots', t => {
t.snapshot(1)
t.snapshot(2)
})
And then reorder the arguments, both assertions will fail.
Changing the test titles will definitely have unintended consequences. The problem with concurrent attempts is that each attempt should start at the same order number, else AVA won't be able to compare snapshots. We could make concurrent attempts work, but if multiple attempts used snapshots then we can't save both:
test('concurrent attempts', t => {
const attempt = t2 => {
t2.snapshot(true)
}
const [first, second] = await Promise.all([t.try(attempt), t.try(attempt)])
first.commit()
second.commit() // This must fail!
})
test('serial attempts', t => {
const attempt = t2 => {
t2.snapshot(true)
}
(await t.try(attempt)).commit()
(await t.try(attempt)).commit() // This is fine
})
So yea, we can make concurrent attempts work! But we need some housekeeping for the snapshots to work correctly.
It might be nicer to have special mode for attempts via
{ inline: true }
, but I dont think I'm familiar enough with the code base to make that change.
That's fine, I can pick that up.
The commit() and discard() functions are split. Counting of attempt is increased by the increment value. Behavior of try, commit() and discard() are more implicit. Removes unneeded fields from commit().
@qlonik do you mean that the snapshots from the first attempt are recorded, and then the test fails? I don't think that's any different from regular snapshots which are recorded before an assertion fails. |
Yes, I meant that. I think it's okay then. |
Though you may start multiple attempts.
@qlonik I've hopefully fixed the tests and made it so you can't run assertions outside of an active attempt. I'm planning to make |
The changes look good to me. The only thing I have to add and I'm not sure if it exists is the |
Oh I don't really know about JSDoc. But if it comes up we can update the definition for that. I'll prefix the comments with "Requires opt-in." though. |
@qlonik I'll file the follow-up issues later, but for now 🎉 let's merge this! Thank you so much for your hard work on this, and for bearing with me over all these months. |
Yay 🎉! Thank you for helping with the feature too. |
See https://github.com/orgs/avajs/projects/1 for the follow-up work. Let me know if I missed anything 😄 |
I think the case when someone tries to use parent's assertions while inside the attempt is not addressed. This is the only discussion we had about it so far:
|
No I dealt with that: bb458be |
Ah, okay, I didn't notice. 👍 |
Hello. Some time ago there was a discussion in the #1692 issue. I tried to look around the code and see what could be done. I tried doing this but I'm not sure if it is the best solution.
This method creates new instance of
Test
class and runs the attempted assertion as part of that and then it updates main test if the attempted test passes or fails.There were suggestions in the issue to create some
Host
class, whichTest
class will extend. SuchHost
class will only care about running assertions, whileTest
class will care about counting assertions and taking care of snapshots. I tried withHost
class at first, however, I found that assertions are getting bound to theTest
, and I was not sure which parts ofTest
need to be moved toHost
and which can stay, without breaking assertions.Also, creating new
Host
class, would require to re-implement therun()
function and all of the edge cases, which are already figured out in the current implementation ofTest()
.There were also mentions about taking care of
plan()
assertion and about recursiveness oftry()
assertion. Also, snapshots were excluded from discussion, but I think they should be talked about too.Since the current implementation of
Test
can handle all of these cases, it means reusingTest
class as an instance fortry()
function should allow for those three things to work. There was also a comment about supporting the.discard()
on promise returned from.try()
, which is also added.With the current implementation, each attempt has to be explicitly either accepted with
.commit()
or declined with.discard()
. When the result is committed to, it increase assertion counter and copies the log of the attempt into main test. When the result is discarded, the assertion counter is not increased. It seems that it would be possible to remove explicit discard on the attempt result. However, in that case, there will be a requirement that user need to have at least one call to.commit()
in order to pass the test.I'm open to some suggestions about current implementation and to guidance how to make
Host
class work, if this is more desirable way.Example usages with current implementation:
TODO:
(Updated from #1947 (comment))
t.context
)