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

Rewrite RTC ICE and DTLS transport tests with alternative dependencies (Rebased) #13886

Closed

Conversation

soareschen
Copy link
Contributor

This is a rebased version of PR #9424.


for(const cert of certs) {
assert_true(cert instanceof ArrayBuffer,
'Expect certificate elements be instance of ArrayBuffer');
Copy link
Contributor

Choose a reason for hiding this comment

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

English: "Certificates should be represented as ArrayBuffers".

function validateConnectingDtlsTransport(dtlsTransport) {
if (dtlsTransport.state !== 'connected') {
assert_array_equals(dtlsTransport.getCertificates(), [],
'Expect DTLS certificates be initially empty until become connected');
Copy link
Contributor

Choose a reason for hiding this comment

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

English: "The DTLS certificate list should be empty before the transport is connected".

@foolip foolip mentioned this pull request Nov 3, 2018
Copy link
Contributor

@youennf youennf left a comment

Choose a reason for hiding this comment

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

Nice!
Some nits and one question about webrtc/tools/.eslintrc.js below.

// The following helper functions are called from RTCTransport-helper.js:
// waitForConnectedState
// getDtlsTransportFromSctpTransport
// getDtlsTransportsFromSenderReceiver
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need those comments?
I am fearing that they will be obsolete when/if we start updating the tests and forget to update these comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the helper functions are exposed as global variables, I thought it might be helpful for readers to find out where they come from. If people don't find it useful, we may remove them in new PRs.

webrtc/RTCDtlsTransport-getRemoteCertificates.html Outdated Show resolved Hide resolved
webrtc/RTCTransport-helper.js Outdated Show resolved Hide resolved
@@ -111,6 +114,7 @@ module.exports = {
getUserMediaTracksAndStreams: true,
performOffer: true,
Resolver: true,
addTrackOrTransceiver: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I did not know about that file.
Should I have updated it when adding some helper functions in RTCPeerConnection-helpers.js?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The lint tool was intended as quick way to find out common errors such as undefined variable without being flooded by warnings on the global helpers. It has not been updated for some time so running lint now is not very useful anyway. We probably should either add all global variables to it, or remove the lint tools from the repo.

Copy link
Contributor

Choose a reason for hiding this comment

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

Either we should enforce running the lint tool for every PR or we should remove it.
Do you think we should do with one or the other?

@sideshowbarker
Copy link
Contributor

stability checker failing https://travis-ci.org/web-platform-tests/wpt/jobs/450733046

Copy link
Contributor

@alvestrand alvestrand left a comment

Choose a reason for hiding this comment

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

Found a few more nits that need fixing. The PC leak is important.

assert_greater_than(candidates.length, 0,
'Expect at least one ICE candidate returned from get*Candidates()');

for(const candidate of candidates) {
assert_true(candidate instanceof RTCIceCandidate,
'Expect candidate elements to be instance of RTCIceCandidate');
assert_true(candidate instanceof RTCIceCandidate,
Copy link
Contributor

Choose a reason for hiding this comment

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

Here and below, indentation seems to have turned odd. Please fix.

// for example would have it implemented but only usable in unified plan,
// which means it would fail the tests when using default peer connection config
function canAddTransceiver() {
const pc = new RTCPeerConnection();
Copy link
Contributor

Choose a reason for hiding this comment

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

Please close this PeerConnection in cleanup.

Copy link
Member

Choose a reason for hiding this comment

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

We should not feature detect in tests IMHO, as it ends up testing different things on different browsers.

addTransceiver is in the spec; Chrome should be running these tests with unified-plan enabled already.

@soareschen
Copy link
Contributor Author

All issues fixed. Is there any reason the CI keep failing with Chrome and Firefox Nightly? Any way we can get this merged?

@sideshowbarker
Copy link
Contributor

All issues fixed. Is there any reason the CI keep failing with Chrome and Firefox Nightly? Any way we can get this merged?

See #7660 and #14033. So we can merge this PR after review approval

// on the supported method on a browser.
// - If addTransceiver(track) is usable, use that.
// - If addTrack(track, mediastream) is available, use that.
function addTrackOrTransceiver(pc, track, mediaStream) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this one?
It makes sense to support older tests for older browsers but for new tests/new constructs, we can probably hope to have transceivers available. Maybe we do want to test these new constructs with plan B as well, I guess this is fine.

Maybe we should rename it to addTransceiverOrTrack since we first try to add a transceiver and fall back to tracks otherwise.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @youennf this helper is unneeded, unwanted even, because it ends up testing different things on different browsers. I vote we call addTransceiver or addTrack directly, whichever we intend to test with.

// - If addTrack(track, mediastream) is available, use that.
function addTrackOrTransceiver(pc, track, mediaStream) {
if (canAddTransceiver()) {
return pc.addTransceiver(track);
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably pass the mediaStream to addTransceiver so as to be consistent with addTrack

return pc.addTrack(track, mediaStream);
} else {
throw new Error('This test requires either addTrack or addTransceiver implemented');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can simplify and just call addTrack without check. It will anyway throw.

Copy link
Member

@jan-ivar jan-ivar left a comment

Choose a reason for hiding this comment

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

This may be subjective, but I'd prefer not organizing around helper functions the way this does.

assert_greater_than(certs.length, 0,
'Expect DTLS transport to have at least one remote certificate when connected');

for(const cert of certs) {
Copy link
Member

Choose a reason for hiding this comment

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

for space (

// for example would have it implemented but only usable in unified plan,
// which means it would fail the tests when using default peer connection config
function canAddTransceiver() {
const pc = new RTCPeerConnection();
Copy link
Member

Choose a reason for hiding this comment

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

We should not feature detect in tests IMHO, as it ends up testing different things on different browsers.

addTransceiver is in the spec; Chrome should be running these tests with unified-plan enabled already.

// on the supported method on a browser.
// - If addTransceiver(track) is usable, use that.
// - If addTrack(track, mediastream) is available, use that.
function addTrackOrTransceiver(pc, track, mediaStream) {
Copy link
Member

Choose a reason for hiding this comment

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

I agree with @youennf this helper is unneeded, unwanted even, because it ends up testing different things on different browsers. I vote we call addTransceiver or addTrack directly, whichever we intend to test with.

assert_true(dtlsTransport instanceof RTCDtlsTransport,
'Expect sctp.transport to be an RTCDtlsTransport');

return dtlsTransport;
Copy link
Member

Choose a reason for hiding this comment

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

I confess I'm personally not a fan of tests hidden in helpers and helpers that otherwise merely substitute JS for English—I prefer helpers that encapsulate whole sub-problems, usually with some output.

I find it much easier to read tests that call spec-documented APIs directly. Less to learn.

Others may disagree, but I'd rather see repetition in the tests themselves than too many helpers.

assert_true(iceTransport instanceof RTCIceTransport,
'Expect dtlsTransport.iceTransport to be an RTCIceTransport');

return iceTransport;
Copy link
Member

Choose a reason for hiding this comment

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

ditto with this one.

// in pc.There may be more than one DTLSTransport if there are
// multiple underlying transports, such as when not using max-bundle,
// or when not using RTCP multiplexing.
function getDtlsTransportsFromSenderReceiver(pc) {
Copy link
Member

Choose a reason for hiding this comment

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

These helpers collectively abstract away the peer connection APIs we're testing. That may be the right approach for application development, but for writing API tests, I prefer repetition operating on the APIs we're actually testing.

This applies to the remaining helpers here as well.

My 2 cents.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it depends on how complicated it is to figure how to perform an operation correctly. There are many things in WebRTC that are not as straightforward as "I want to do X, therefore I'd just call method Y".

I have removed the helpers and make them inline. The inline transport extractors are simplified and not doing any thorough checking or assertions anymore. Hope this will be more readable, easier to verify correctness, and easy for copy/paste for new test cases.

@jan-ivar
Copy link
Member

These are tests meant to enforce that all browsers behave the same way. We want them readable like prose.

@soareschen
Copy link
Contributor Author

Added suggested changes. Just want to point out that the motivation for addTrackOrTransceiver came out when we were back in the "old" days when addTransceiver was not well supported in most browsers. At that time people were concerned of the tests on interfaces such as RTCRtpSender are failing because they are using addTransceiver, even though browsers have implemented alternative methods addTrack or addStream.

The tests here are neither testing addTrack nor addTransceiver. I prefer not have helpers like addTrackOrTransceiver just to make more tests passing. But I also prefer people not raising this or similar issues because different browsers have different methods implemented, and they all want the tests to pass.

Let's hope history never repeat again for issues similar to whether to use addStream / addTrack(track) / addTrack(track, mediaStream) / addTransceiver to make tests pass, and everyone can move on to just assume addTransceiver / addTrack are there.

Copy link
Member

@jan-ivar jan-ivar left a comment

Choose a reason for hiding this comment

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

Thanks for addressing this, I like this direction! Some nits and comments, particularly around the tests around number of transports seems wrong.

...pc2.getReceivers()
]
.map(senderReceiver => senderReceiver.transport)
.filter(dtlsTransport => dtlsTransport); // filter null/undefined
Copy link
Member

Choose a reason for hiding this comment

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

Why allow null and undefined? All sender/receiver.transport objects should be created at this point, no?

Applies throughout.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I probably misremembered or referred to older spec where some transport may be null and still spec-compliant. Removed that assumption now.

.map(senderReceiver => senderReceiver.transport)
.filter(dtlsTransport => dtlsTransport); // filter null/undefined

assert_greater_than_equal(dtlsTransports.length, 2, 'expect to find at least 2 RTCDtlsTransports');
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't match what you had before, which used new Set() instead of an array, and then tested the total number of transports in that set, allowing for duplicates, matching how I read transport: "When bundling is used, multiple RTCRtpReceiver objects will share one transport".

I interpret the spec here to mean multiple .transport attributes may point to the exact same JavaScript object when bundling is used. It should work if we use:

const dtlsTransports1 = new Set([...pc1.getSenders(), ...pc1.getReceivers()]);

...and test for dtlsTransports1.size >= 1, but we probably shouldn't mix pc1 and pc2 in the same set as these should never share transport objects. That would be a bug, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes the semantics kind of changed a bit, as I tried to avoid copy-pasting too much code when inlining. Though the original test don't really test that the transport objects in different pcs are different though, and validating that is a bit complicated and redundant.

I have simplified the code to just skip all the checkings. They just assume the browser is spec compliant and create a transport object after setting local description. The consequence is mainly error messages in tests is more obscure, for trying to access fields of a null or undefined object for current implementations.

}

await waitForConnectedState(pc1);
await waitForConnectedState(pc2);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Fyi, my eyes burn a bit when I see this pattern, as it looks like a potential race on the surface. I'd prefer:

    await Promise.all([waitForConnectedState(pc1), waitForConnectedState(pc2)]);

or

    await Promise.all([pc1, pc2].map(waitForConnectedState));

I know it's fine as is, but from its name alone, I would not have expected waitForConnectedState to check for state synchronously:

async function waitForConnectedState(pc) {
  if (pc.connectionState === 'connected') {
    return;
  }

Maybe I'm used to similarly named functions that don't—they too are useful, just different, as they wait for the next time a particular state rolls around.

So while this is fine as is, I worry about people new to async/await copying this pattern. Tests often serve as examples. My 2 cents.

Applies throughout.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed it to use Promise.all(). I don't think it matter in this case, as the operations are just waiting for another concurrent operation that are already running.

Async functions always return a promise. So the caller should still resume asynchronously unless they are ignoring the promise.

So while this is fine as is, I worry about people new to async/await copying this pattern. Tests often serve as examples. My 2 cents.

IMO most of the test code are pretty bad examples for writing good application code, even though they may be considered good test code. If we want WPT to serve as examples for using the spec APIs, they should probably written in a much different way.

.filter(dtlsTransport => dtlsTransport) // filter null/undefined
.map(dtlsTransport => dtlsTransport.iceTransport);

assert_greater_than_equal(iceTransports.length, 2, 'expect to find at least 2 RTCIceTransports');
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Wouldn't it be fewer lines to just assert each of the two pcN.sctp.transport.iceTransports?

await pc1.setLocalDescription(offer)
await pc2.setRemoteDescription(offer)
const answer = await pc2.createAnswer()
await pc1.setRemoteDescription(answer)
Copy link
Member

Choose a reason for hiding this comment

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

Tip: could be inlined, for fewer names polluting the cognitive stack space:

await pc1.setRemoteDescription(await pc2.createAnswer());

Also, missing ; semicolons at EOLs here.

if (connectionState === 'connected') {
resolve();
} else if (['closed', 'failed'].includes(connectionState)) {
reject(new Error(`one of DTLS/ICE transports transition to unexpected state ${connectionState}`));
Copy link
Member

@jan-ivar jan-ivar Nov 20, 2018

Choose a reason for hiding this comment

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

I advocate wrapping non-promise code as tightly as possible, which means avoiding any kind of application code inside promise constructors.

The WPT test harness adds a second reason: the awkward t.step_func() wrapper is needed to correlate spurious event handlers with a particular test, and it's missing here. Unfortunately, this would mean adding t as an additional argument to waitForConnectedState, which gets ugly.

Instead, I would recommend something like this:

while (pc.connectionState != 'connected') {
  await new Promise(r => pc.addEventListener('connectionstatechange', r, {once: true}));
  if (['closed', 'failed'].includes(pc.connectionState)) {
    throw new Error(`DTLS/ICE transition to unexpected state ${pc.connectionState}`);
  }
}

This way, context is preserved by promise_test and we don't need t.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with your proposition that inside the promise constructor the code should be as minimal as possible. However I do not agree with the proposed solution. Given that the timing for promise resolution is non-deterministic, you may miss an event firing if after the first event, a second event fires before the continuation following the await is executed. Instead of keeping the promise constructor absolute minimal, I think the better way to interpret is that code inside the promise constructor should be more carefully scrutinized, and having less code is a way for verifying the correctness.

I have added a try catch inside the callback to make sure the promise does not get unresolved forever due to exceptions. There are definitely better ways to do this, but given the many conflicting constraints we have for writing tests, I think that should be enough for this particular case.

@alvestrand
Copy link
Contributor

@soareschen will you update?

@foolip
Copy link
Member

foolip commented Dec 20, 2018

This PR is blocked on the required "Travis CI - Pull Request" check after #14499. In order to trigger it, I will close and reopen this PR.

@foolip
Copy link
Member

foolip commented Dec 20, 2018

This PR is running into a Taskcluster infra issue, see #14618. If, when this is reviewed and ready to merge, there's still trouble with Taskcluster, please poke me, @Hexcles or @jgraham and we can merge it anyway.

@alvestrand
Copy link
Contributor

There's still a lot of dependency on sctp.transport.
Given the pattern of implementation, it seems better to get the transport via senders/receivers.

@alvestrand
Copy link
Contributor

Closing as "likely obsolete".

@alvestrand alvestrand closed this Apr 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants