-
Notifications
You must be signed in to change notification settings - Fork 3k
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
New testScheduler.run() auto-scheduler injection method and time progression syntax #3596
Conversation
// remove it from the original array, not our copy | ||
flushTests.splice(i, 1); | ||
this.assertDeepEqual(test.actual, test.expected); | ||
} |
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.
Done so that I could assert that everything was correctly flushed
case ' ': | ||
// Whitespace no longer advances time | ||
if (!runMode) { |
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'm not a huge fan of using a flag like this, but it seemed to be the easiest. The alternative of having two copies of the function seems like a worse nightmare and increases potential for someone to fix a bug in one but forget about the other.
|
||
export class AsyncScheduler extends Scheduler { | ||
public static delegate?: Scheduler; |
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.
While I'm open to alternatives, it turned out to be rather difficult to injection just for AsyncScheduler because TestScheduler extends VirtualScheduler which also extends AsyncScheduler, so changes made here also apply to TestScheduler and many attempts I had ended in just infinite recursion 😆
Do you have an example of a diagram in which you don't have to subtract 1 millisecond from the time you want to progress? One way of looking at it would be that in marble diagrams like |
You could absorb the extra needed ms before the duration. '-a|'
'- 100ms (a|)'
// instead of
'-- 99ms (a|)' Or if the input diagram also has an extra 1ms '-a 100ms b|'
'-- 100ms b|'
Can you elaborate? Currently |
|
||
testScheduler.run(({ cold, hot, flush, expectObservable, expectSubscriptions }) => { | ||
const output = cold('10.2ms a 1.2s b 1m c|'); | ||
const expected = ' 10.2ms a 1.2s b 1m c|'; |
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 test is fine, but I feel we should have more (any) unit tests around just the parseMarbles
method. Something to assert that the diagrams are being converted to the appropriate object structures. It would make this test more sound, IMO.
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('should provide the correct helpers', () => { |
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're missing the time
helper, which was designed to convert a marble like -----|
to a number in virtual ms.
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 discussed and we're going to skip it for now as it's not clear if it actually adds enough value in a world with a single frame = 1ms, there's potential debate about what syntax it should support (e.g. next values or errors?), and it's a non-breaking change to include it later.
Overall it looks good. Just missing one helper and we should probably add some unit tests around parsing marbles. |
Basically, I was trying to think about how the explicitly-specified durations could be related to frames in a manner that would be conceptually easier to understand/explain. The current behaviour makes sense when you think about the implementation. Consider this diagram:
However, anyone looking at One way of achieving this might be to deem that each frame/character has a default/implied duration of 1 ms, but if the character is followed by an explicit time, that explicit time specifies the frame's/character's duration. So this:
would be equivalent to this:
But what that would mean for synchronous groups, I don't know. Synchronous groups are a pain point. Every single dev I've introduced to marble tests has misunderstood how virtual time is handled for synchronous groups. Anyway, it's too late, now, as 6.0.0 has just been published and any changes along these lines would be breaking. |
100% Sorry we wanted to get this in ASAP since, based on the massive amount of iteration and bikeshedding that has already happened I’m fairly confident we won’t come up with a solid solution to these other problems in the short term. If you have interest in this though, please do continue to experiment and think of solutions. See the RFC doc in the original post above for inspiration to some of the problems we want to solve eventually. We can chat separately too. |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Huge thank you to @benlesh for the countless hours helping me bikeshed testing infra in general and future related proposals, and @kwonoj for his input and work on rx-sandbox which provided a lot of the inspiration.
This PR adds a new
run
method on instances of TestScheduler. It accepts a callback which is then synchronously called with a single argument containing the helpershot
(short forcreateHotObservable
),cold
(short forcreateColdObservable
),expectObservable
,expectSubscription
, andflush
.Inside that callback you can use these helpers similar to how you would with the existing TestScheduler, except this new API allows us to introduce some changes in a non-breaking way. The changes listed below only apply inside the
testScheduler.run(callback)
callback.Aside from the hot/cold name changes:
-a 100ms b-|
TestScheduler.frameTimeFactor = 1
maxFrames = Number.POSITIVE_INFINITY
Remember: these changes do not apply to usage of TestScheduler outside of
testScheduler.run()
. If you calltestScheduler.expectObservable()
it will not have this new behavior and features..Time progression syntax
The new time progression syntax takes inspiration from the CSS duration syntax. It's a number (int or float) immediately followed by a unit; ms (milliseconds), s (seconds), m (minutes). e.g.
100ms
,1.4s
,5.25m
.When it's not the first character of the diagram it must be padded a space before/after to disambiguate it from a series of marbles. e.g.
a 1ms b
needs the spaces becausea1msb
will be interpreted as['a', '1', 'm', 's', 'b']
where each of these characters is a value that will be next()'d as-is.NOTE: Very often you have to subtract 1 millisecond from the time you want to progress because the alphanumeric marbles (representing an actual emitted value) advance time 1 virtual frame. This can be very unintuitive and frustrating.
We could change it so that value marbles no longer advance time, but this then has the affect of making marble diagrams less likely to align without adding extra whitespaces, similar to why grouping syntax advances the number of frames equal to the character count, e.g.
(abc)
advances time 5 frames. In previous discussions around making that change it was decided against, but this quirk was not known then so it might be wise to discuss?Additional questions
The future
One of the goals was to change as little of the code as possible (to get this out quicker). The existing TestScheduler infrastructure is badly in need of a complete overhaul (all of the schedulers, to be honest). There has been numerous very involved attempts at doing so over many many months, including completely reimagining marble diagrams and ditching them altogether for a builder-pattern.
I won't spell out here all of the problems with the existing TestScheduler, but some of them are documented in this rough RFC that was based on numerous sessions between Ben Lesh and I. We were originally super optimistic with what we came up with, but as I spent the time and implemented it I kept running into cases we hadn't accounted for. And after revising, and revising, numerous times we unfortunately both came to the conclusion it had become something neither of us were happy with. It may not seem like it, but boy there are a lot of complexities to making a marble syntax that works and is aesthetically beneficial. While we will continue to bikeshed how to solve these problems, it was decided that we should provide this PR as an incremental change to at least solve some of the most common complaints.