Skip to content
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

FLUID-5936: fix bug in fluid.textToSpeech.checkTTSSupport, rewrite tests to use IoC testing #732

Merged
merged 45 commits into from
Sep 2, 2016

Conversation

waharnum
Copy link
Member

@waharnum waharnum commented Aug 4, 2016

This PR addresses the bug described at https://issues.fluidproject.org/browse/FLUID-5936

Additionally, it rewrites the existing tests (but does not change what they test) to use the IoC testing framework to make test hangs related to async timeouts or non-firing events more visible.

@amb26
Copy link
Member

amb26 commented Aug 4, 2016

These tests hang for me at "Test Pause and Resume Events":

Thu Aug 04 2016 20:49:42 GMT+0100 (GMT Daylight Time): Test case listener has not responded after 5000ms - at sequence pos 5 of 6 sequence element Object {listener: "fluid.tests.textToSpeech.testResume", args: Array[1], event: "{tts}.events.onResume"} of fixture Test Pause and Resume Events

Configuration: Chrome 52.0.2743.116 m (64-bit) on Windows 7 64-bit

}
};

// fluid.setLogging(fluid.logLevel.TRACE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray comment

@waharnum
Copy link
Member Author

waharnum commented Aug 4, 2016

Thanks @amb26 - booting up my Windows VM to check. @jobara has mentioned there was an issue with pause/resume in Chrome that caused that test to be skipped via browser detection previously, it's possible that's now Windows-only.

@waharnum
Copy link
Member Author

waharnum commented Aug 5, 2016

The best way of achieving reliable tests with the current implementation (in both senses I used it above) appears to be short delays in issuing pause/resume commands, which also more closely emulates a typical user interaction with things like pause/resume controls.

From what research I've done (and what information is easily available), I think that various browser implementations of TTS have somewhat differing asynchronous behaviours, both within the different browsers, and within the browser's interaction with the operating system APIs to perform TTS.

This leads to differences in when certain TTS events the wrapper component depends on to fire its own events occur, both generally between different browsers/OSs, and in individual instances.

A long way of saying that introducing a short delay when invoking pause/resume leads to reliably passing tests on OS X and Windows.

@amb26
Copy link
Member

amb26 commented Aug 5, 2016

This doesn't strike me as very reliable. If the component is not responsive to events on a certain timescale, this should be built into its implementation rather than left as an artefact of the test cases. We should work to absorb whatever cross-platform slop there is into our own interface rather than special-case our testing requirements.

@waharnum
Copy link
Member Author

waharnum commented Aug 5, 2016

I'm of two minds about this (and trying to not have one or both of them influenced by the mind that would like to move on from the pit of cross-browser/cross-OS TTS issues...):

Argument for Leaving the Component As-Is

I think it's less that the component itself is not responsive to events on a certain timescale than that underlying browser TTS events the component relies upon do not appear to be responsive to events on a certain timescale. I know this may sound like developer finger-pointing, but I think it has at least some basis in reality. I believe you'd see similar issues non-asychronously issuing rapid pause/resume commands through the TTS interface - events get lost, race conditions occur, etc.

Argument for Absorbing Cross-Platform Slop

I think it would probably be possible to make some improvements as part of this PR that might help to deal with the cross-platform issues. In particular, I haven't traced all the scenarios fully, but I believe there may be instances where internal component state can diverge from the state of the browser TTS interface around whether or not speech is playing/paused.

So I can try refactoring to:

Writing this out makes me realize I should be of the second mind, as I'm probably in the best position to do that work I'll be right now, as opposed to if I step away from it for weeks until I "have more time". 😛

@waharnum
Copy link
Member Author

waharnum commented Aug 8, 2016

I've moved the approach of issuing asynchronous pause/resume commands to the speechSynthesis object from the tests to the main component, and refactored the invokers to use this approach (the fluid.textToSpeech.asyncSpeechSynthesisControl function) rather than invoking the speechSynthesis commands directly.

A functionally imperceptible delay via setTimeout (10ms) before issuing commands results in consistently passing tests in TTS-supporting browsers on both Mac and Windows.

@waharnum
Copy link
Member Author

@amb26, is there anything else that you think should be done on this one? It would be nice to get it in for the 2.0 release.

@waharnum
Copy link
Member Author

update to say that I'm also looking at https://issues.fluidproject.org/projects/FLUID/issues/FLUID-5735 as part of this - tests are still failing when run as part of all-tests, but I believe I've pinned down why and should have a fix shortly.

…e all-tests runner from not handling these tests properly
@waharnum
Copy link
Member Author

waharnum commented Aug 15, 2016

I believe FLUID-5735 should now be fixed as part of this PR by commit 5ca02a8 - the issue was with the approach taken to deciding whether or not to execute the test. Wrapping it in the fluid.textToSpeech.checkTTSSupport promise caused the all-tests runner to believe it was OK to move on from this test to the next one, which caused this test to fail (0 assertions run) and then causing the next test in line to fail with an incorrect number of assertions, as the tests that were being run as a result of the promise resolving were then run as part of the next test context.

I resolved this by wrapping the promise-based structure in an asyncTest structure; this results in the intended outcome of the tests being run or skipped based on the results of the promise ("does the test environment truly support TTS?"), while also playing nicely with the all-tests environment.

@@ -142,6 +142,16 @@ var fluid_2_0_0 = fluid_2_0_0 || {};
}
});

// Issue functions to the speechSynthesis interface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very short line wrapped comments - perhaps an IDE setting run amok?

}
};

fluid.textToSpeech.issueControlRequest = function (that, modelBoolPath, controlName, invert) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be eliminated and the invokers replaced with a "changePath" record - http://docs.fluidproject.org/infusion/development/ChangeApplierAPI.html#declarative-style-for-triggering-a-change

@waharnum
Copy link
Member Author

waharnum commented Sep 1, 2016

@amb26 - I've implemented throttle-based control, as you recommended, and reduced the dependence on functions for managing control vs. model paths & model listeners. At this point we should decide if the changes could go in their current form for Infusion 2.0 or if further work is needed - I don't think I have more cycles to spend on this.

@waharnum
Copy link
Member Author

waharnum commented Sep 1, 2016

I also want to flag the comment at #732 (comment) - if we remove the non-model events for pause/resume, we'll need to update the documentation as well to say those events have been removed and that the alternative is to use model listeners.

@@ -170,7 +242,11 @@ var fluid_2_0_0 = fluid_2_0_0 || {};
paused: false
};

if (!that.queue.length) {
if (that.queue.currentUtterance) {
that.queue.currentUtterance = undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually meant to put it in the queue item, rather than to rebase the entire queue structure.
So, on line 280 we would have that.queue.push({text: text, utterance: toSpeak})
queue can then just go back to being a [] and the rest of the changes reverted

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree this is better; I had to refactor the queue behaviour a little so that.queue.shift happens at handleEnd rather than handleStart or we run into the same premature garbage collection behaviour in Safari that we were seeing previously. I don't think this is a problematic change (it also lets handleStart simply be a changePath record rather than a function), but please let me know if you foresee an issue with that behavioural change.

@amb26
Copy link
Member

amb26 commented Sep 1, 2016

Thanks, @waharnum - changes look great, with a minor tweak. Thanks for flagging the required docs update - this change is ok at present since we haven't yet made a release with this component included.

@waharnum
Copy link
Member Author

waharnum commented Sep 1, 2016

Thanks @amb26 - I'll make that change to the queue structure (and fix the lint), and do a separate docs pull request to remove the onPause/onResume events.

@waharnum
Copy link
Member Author

waharnum commented Sep 1, 2016

@amb26 amb26 merged commit fe384eb into fluid-project:master Sep 2, 2016
@amb26
Copy link
Member

amb26 commented Sep 2, 2016

Hey there @waharnum - thanks so much for the marathon work on this, and pushing it the extra mile to the end!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants