Skip to content

Commit

Permalink
Basic layout tests for WebUSB.
Browse files Browse the repository at this point in the history
This change relands https://codereview.chromium.org/1734753003/ with
additional layout tests that provide basic coverage of all the functions
defined by the WebUSB API. The tests are implemented by providing mock
implementations of the Mojo services that Blink depends on to provide
the WebUSB API, allowing the entire test implementation to be done in
Javascript.

Discovered a number of bugs in isochronous packet handling while writing
these.

BUG=492204

Review URL: https://codereview.chromium.org/1730403006

Cr-Commit-Position: refs/heads/master@{#378578}
  • Loading branch information
reillyeon authored and Commit bot committed Mar 1, 2016
1 parent c6688e1 commit d20e8f0
Show file tree
Hide file tree
Showing 9 changed files with 994 additions and 38 deletions.
7 changes: 7 additions & 0 deletions content/renderer/usb/web_usb_device_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ void OnIsochronousTransferIn(
auto scoped_callbacks = callbacks.PassCallbacks();
scoped_ptr<blink::WebUSBTransferInfo> info(new blink::WebUSBTransferInfo());
info->data.assign(data);
info->status =
blink::WebVector<blink::WebUSBTransferInfo::Status>(packets.size());
info->packetLength = blink::WebVector<uint32_t>(packets.size());
info->bytesTransferred = blink::WebVector<uint32_t>(packets.size());
for (size_t i = 0; i < packets.size(); ++i) {
switch (packets[i]->status) {
case device::usb::TransferStatus::COMPLETED:
Expand All @@ -204,6 +208,9 @@ void OnIsochronousTransferOut(
mojo::Array<device::usb::IsochronousPacketPtr> packets) {
auto scoped_callbacks = callbacks.PassCallbacks();
scoped_ptr<blink::WebUSBTransferInfo> info(new blink::WebUSBTransferInfo());
info->status =
blink::WebVector<blink::WebUSBTransferInfo::Status>(packets.size());
info->bytesTransferred = blink::WebVector<uint32_t>(packets.size());
for (size_t i = 0; i < packets.size(); ++i) {
switch (packets[i]->status) {
case device::usb::TransferStatus::COMPLETED:
Expand Down
79 changes: 46 additions & 33 deletions third_party/WebKit/LayoutTests/resources/mojo-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,66 @@

// Fix up the global window.define, since all baked-in Mojo modules expect to
// find it there. This define() also returns a promise to the module.
function define(name, deps, factory) {
return new Promise(resolve => {
mojo.define(name, deps, (...modules) => {
let result = factory(...modules);
resolve(result);
return result;
});
let define = (function(){
let moduleCache = new Map();

return function(name, deps, factory) {
let promise = moduleCache.get(name);
if (promise === undefined) {
// This promise must be cached as mojo.define will only call the factory
// function the first time the module is defined.
promise = new Promise(resolve => {
mojo.define(name, deps, (...modules) => {
let result = factory(...modules);
resolve(result);
return result;
});
});
moduleCache.set(name, promise);
}
return promise;
}
})();

define('Mojo Helpers', [
'mojo/public/js/core',
'mojo/public/js/router',
'mojo/public/js/support',
'content/public/renderer/service_provider'
], (core, router, support, serviceProvider) => {
add_completion_callback(() => {
serviceProvider.clearServiceOverridesForTesting();
});
}

return {
core: core,
router: router,
support: support,

// |serviceProvider| is a bit of a misnomer. It should probably be
// called |serviceRegistry|, so let's call it that here.
serviceRegistry: serviceProvider,
};
});

// Returns a promise to an object that exposes common Mojo module interfaces.
// Additional modules to load can be specified in the |modules| parameter. The
// result will contain them, in the same order, in the |modules| field.
function loadMojoModules(name, modules = []) {
return define('Mojo layout test module: ' + name, [
'mojo/public/js/core',
'mojo/public/js/router',
'mojo/public/js/support',
'content/public/renderer/service_provider',
].concat(modules), (core, router, support, serviceProvider, ...rest) => {
return {
core: core,
router: router,
support: support,

// |serviceProvider| is a bit of a misnomer. It should probably be
// called |serviceRegistry|, so let's call it that here.
serviceRegistry: serviceProvider,

modules: rest,
};
return define('Mojo modules: ' + name,
[ 'Mojo Helpers' ].concat(modules),
(mojo, ...rest) => {
mojo.modules = rest
return mojo;
});
}

function mojoTestCleanUp(mojo) {
mojo.serviceRegistry.clearServiceOverridesForTesting();
}

// Runs a promise_test which depends on the Mojo system API modules available to
// all layout tests. The test implementation function is called with an Object
// that exposes common Mojo module interfaces.
function mojo_test(func, name, properties) {
promise_test(() => loadMojoModules(name).then(mojo => {
let result = Promise.resolve(func(mojo));
let cleanUp = () => mojoTestCleanUp(mojo);
result.then(cleanUp, cleanUp);
return result;
return Promise.resolve(func(mojo));
}), name, properties);
}

Expand Down
17 changes: 17 additions & 0 deletions third_party/WebKit/LayoutTests/usb/mock-services.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="../resources/mojo-helpers.js"></script>
<script src="resources/fake-devices.js"></script>
<script src="resources/usb-helpers.js"></script>
<script>
'use strict';

usb_test(usb => {
assert_true(usb instanceof Object);
assert_true(usb.DeviceManager instanceof Object);
assert_true(usb.Device instanceof Object);
assert_true(usb.mockDeviceManager instanceof Object);
assert_true(usb.mockPermissionBubble instanceof Object);
}, 'USB Mojo bindings and mock interfaces are available to tests.')
</script>
120 changes: 120 additions & 0 deletions third_party/WebKit/LayoutTests/usb/resources/fake-devices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use strict';

function fakeUsbDevices() {
return define('Fake USB Devices', [
'device/usb/public/interfaces/device.mojom',
], device => Promise.resolve([
{
guid: 'CD9FA048-FC9B-7A71-DBFC-FD44B78D6397',
usb_version_major: 2,
usb_version_minor: 0,
usb_version_subminor: 0,
class_code: 7,
subclass_code: 1,
protocol_code: 2,
vendor_id: 0x18d1,
product_id: 0xf00d,
device_version_major: 1,
device_version_minor: 2,
device_version_subminor: 3,
manufacturer_name: 'Google, Inc.',
product_name: 'The amazing imaginary printer',
serial_number: '4',
configurations: [
{
configuration_value: 1,
configuration_name: 'Printer Mode',
interfaces: [
{
interface_number: 0,
alternates: [
{
alternate_setting: 0,
class_code: 0xff,
subclass_code: 0x01,
protocol_code: 0x01,
interface_name: 'Control',
endpoints: [
{
endpoint_number: 1,
direction: device.TransferDirection.INBOUND,
type: device.EndpointType.INTERRUPT,
packet_size: 8
}
]
}
]
},
{
interface_number: 1,
alternates: [
{
alternate_setting: 0,
class_code: 0xff,
subclass_code: 0x02,
protocol_code: 0x01,
interface_name: 'Data',
endpoints: [
{
endpoint_number: 2,
direction: device.TransferDirection.INBOUND,
type: device.EndpointType.BULK,
packet_size: 1024
},
{
endpoint_number: 2,
direction: device.TransferDirection.OUTBOUND,
type: device.EndpointType.BULK,
packet_size: 1024
}
]
}
]
}
]
},
{
configuration_value: 2,
configuration_name: 'Fighting Robot Mode',
interfaces: [
{
interface_number: 0,
alternates: [
{
alternate_setting: 0,
class_code: 0xff,
subclass_code: 0x42,
protocol_code: 0x01,
interface_name: 'Disabled',
endpoints: []
},
{
alternate_setting: 1,
class_code: 0xff,
subclass_code: 0x42,
protocol_code: 0x01,
interface_name: 'Activate!',
endpoints: [
{
endpoint_number: 1,
direction: device.TransferDirection.INBOUND,
type: device.EndpointType.ISOCHRONOUS,
packet_size: 1024
},
{
endpoint_number: 1,
direction: device.TransferDirection.OUTBOUND,
type: device.EndpointType.ISOCHRONOUS,
packet_size: 1024
}
]
}
]
},
]
}
],
webusb_allowed_origins: { origins: [], configurations: [] },
}
]));
}
Loading

0 comments on commit d20e8f0

Please sign in to comment.