Parallelize tests (#17)

* Store a reference to libusb implementation in the context, transfers
and some more places. Remove the global libusb variable.

* Parallelize tests.

* Fix the link in README.
This commit is contained in:
zagrodzki
2017-09-04 16:55:47 +02:00
committed by GitHub
parent f9aba6fab5
commit c113a5e0de
15 changed files with 159 additions and 120 deletions

View File

@@ -13,7 +13,8 @@ Supported platforms include:
- darwin
- windows
This is the release 2.0 of the package [github.com/kylelemons/gousb]. Its API is not backwards-compatible with version 1.0.
This is the release 2.0 of the package [github.com/kylelemons/gousb](https://github.com/kylelemons/gousb).
Its API is not backwards-compatible with version 1.0.
As of 2017-07-13 the 2.0 API is considered stable and 1.0 is deprecated.
[coverimg]: https://coveralls.io/repos/github/google/gousb/badge.svg

View File

@@ -120,12 +120,12 @@ func (c *Config) Interface(num, alt int) (*Interface, error) {
}
// Claim the interface
if err := libusb.claim(c.dev.handle, uint8(num)); err != nil {
if err := c.dev.ctx.libusb.claim(c.dev.handle, uint8(num)); err != nil {
return nil, fmt.Errorf("failed to claim interface %d on %s: %v", num, c, err)
}
if err := libusb.setAlt(c.dev.handle, uint8(num), uint8(alt)); err != nil {
libusb.release(c.dev.handle, uint8(num))
if err := c.dev.ctx.libusb.setAlt(c.dev.handle, uint8(num), uint8(alt)); err != nil {
c.dev.ctx.libusb.release(c.dev.handle, uint8(num))
return nil, fmt.Errorf("failed to set alternate config %d on interface %d of %s: %v", alt, num, c, err)
}

View File

@@ -80,6 +80,7 @@ func (d *DeviceDesc) cfgDesc(cfgNum int) (*ConfigDesc, error) {
// A Device must be Close()d after use.
type Device struct {
handle *libusbDevHandle
ctx *Context
// Embed the device information for easy access
Desc *DeviceDesc
@@ -109,7 +110,7 @@ func (d *Device) Reset() error {
if d.claimed != nil {
return fmt.Errorf("can't reset device %s while it has an active configuration %s", d, d.claimed)
}
return libusb.reset(d.handle)
return d.ctx.libusb.reset(d.handle)
}
// ActiveConfigNum returns the config id of the active configuration.
@@ -119,7 +120,7 @@ func (d *Device) ActiveConfigNum() (int, error) {
if d.handle == nil {
return 0, fmt.Errorf("ActiveConfig() called on %s after Close", d)
}
ret, err := libusb.getConfig(d.handle)
ret, err := d.ctx.libusb.getConfig(d.handle)
return int(ret), err
}
@@ -146,7 +147,7 @@ func (d *Device) Config(cfgNum int) (*Config, error) {
if d.autodetach {
for _, iface := range cfg.Desc.Interfaces {
if err := libusb.detachKernelDriver(d.handle, uint8(iface.Number)); err != nil {
if err := d.ctx.libusb.detachKernelDriver(d.handle, uint8(iface.Number)); err != nil {
return nil, fmt.Errorf("Can't detach kernel driver of the device %s and interface %d: %v", d, iface.Number, err)
}
}
@@ -155,7 +156,7 @@ func (d *Device) Config(cfgNum int) (*Config, error) {
if activeCfgNum, err := d.ActiveConfigNum(); err != nil {
return nil, fmt.Errorf("failed to query active config of the device %s: %v", d, err)
} else if cfgNum != activeCfgNum {
if err := libusb.setConfig(d.handle, uint8(cfgNum)); err != nil {
if err := d.ctx.libusb.setConfig(d.handle, uint8(cfgNum)); err != nil {
return nil, fmt.Errorf("failed to set active config %d for the device %s: %v", cfgNum, d, err)
}
}
@@ -194,7 +195,7 @@ func (d *Device) Control(rType, request uint8, val, idx uint16, data []byte) (in
if d.handle == nil {
return 0, fmt.Errorf("Control() called on %s after Close", d)
}
return libusb.control(d.handle, d.ControlTimeout, rType, request, val, idx, data)
return d.ctx.libusb.control(d.handle, d.ControlTimeout, rType, request, val, idx, data)
}
// Close closes the device.
@@ -207,7 +208,7 @@ func (d *Device) Close() error {
if d.claimed != nil {
return fmt.Errorf("can't release the device %s, it has an open config %d", d, d.claimed.Desc.Number)
}
libusb.close(d.handle)
d.ctx.libusb.close(d.handle)
d.handle = nil
return nil
}
@@ -224,7 +225,7 @@ func (d *Device) GetStringDescriptor(descIndex int) (string, error) {
if descIndex == 0 {
return "", nil
}
return libusb.getStringDesc(d.handle, descIndex)
return d.ctx.libusb.getStringDesc(d.handle, descIndex)
}
// Manufacturer returns the device's manufacturer name.
@@ -283,5 +284,5 @@ func (d *Device) SetAutoDetach(autodetach bool) error {
if autodetach {
autodetachInt = 1
}
return libusb.setAutoDetach(d.handle, autodetachInt)
return d.ctx.libusb.setAutoDetach(d.handle, autodetachInt)
}

View File

@@ -21,10 +21,7 @@ import (
)
func TestClaimAndRelease(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()
t.Parallel()
const (
devIdx = 1
cfgNum = 1
@@ -34,8 +31,14 @@ func TestClaimAndRelease(t *testing.T) {
alt2Num = 0
if2Num = 0
)
c := NewContext()
defer c.Close()
c := newContextWithImpl(newFakeLibusb())
defer func() {
if err := c.Close(); err != nil {
t.Errorf("Context.Close: %v", err)
}
}()
dev, err := c.OpenDeviceWithVIDPID(0x8888, 0x0002)
if dev == nil {
t.Fatal("OpenDeviceWithVIDPID(0x8888, 0x0002): got nil device, need non-nil")
@@ -177,10 +180,7 @@ func TestClaimAndRelease(t *testing.T) {
}
func TestInterfaceDescriptionError(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()
t.Parallel()
for _, tc := range []struct {
name string
cfg, intf, alt int
@@ -189,10 +189,15 @@ func TestInterfaceDescriptionError(t *testing.T) {
{"no interface", 1, 3, 1},
{"no alt setting", 1, 1, 5},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
// Can't be parallelized, depends on the shared global state set before the loop.
c := NewContext()
defer c.Close()
t.Parallel()
c := newContextWithImpl(newFakeLibusb())
defer func() {
if err := c.Close(); err != nil {
t.Errorf("Context.Close(): %v", err)
}
}()
dev, err := c.OpenDeviceWithVIDPID(0x8888, 0x0002)
if dev == nil {
t.Fatal("OpenDeviceWithVIDPID(0x8888, 0x0002): got nil device, need non-nil")
@@ -220,12 +225,9 @@ func (*failDetachLib) detachKernelDriver(h *libusbDevHandle, i uint8) error {
}
func TestAutoDetachFailure(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
fake, done := newFakeLibusb()
defer done()
libusb = &failDetachLib{fake}
c := NewContext()
t.Parallel()
fake := newFakeLibusb()
c := newContextWithImpl(&failDetachLib{fake})
defer c.Close()
dev, err := c.OpenDeviceWithVIDPID(0x8888, 0x0002)
if dev == nil {

View File

@@ -77,6 +77,8 @@ type endpoint struct {
Desc EndpointDesc
Timeout time.Duration
ctx *Context
}
// String returns a human-readable description of the endpoint.
@@ -89,7 +91,7 @@ func (e *endpoint) transfer(buf []byte) (int, error) {
return 0, nil
}
t, err := newUSBTransfer(e.h, &e.Desc, len(buf), e.Timeout)
t, err := newUSBTransfer(e.ctx, e.h, &e.Desc, len(buf), e.Timeout)
if err != nil {
return 0, err
}

View File

@@ -17,7 +17,7 @@ package gousb
func (e *endpoint) newStream(size, count int, submit bool) (*stream, error) {
var ts []transferIntf
for i := 0; i < count; i++ {
t, err := newUSBTransfer(e.h, &e.Desc, size, e.Timeout)
t, err := newUSBTransfer(e.ctx, e.h, &e.Desc, size, e.Timeout)
if err != nil {
for _, t := range ts {
t.free()

View File

@@ -17,9 +17,14 @@ package gousb
import "testing"
func TestEndpointReadStream(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
lib, done := newFakeLibusb()
defer done()
t.Parallel()
lib := newFakeLibusb()
ctx := newContextWithImpl(lib)
defer func() {
if err := ctx.Close(); err != nil {
t.Errorf("Context.Close: %v", err)
}
}()
goodTransfers := 7
go func() {
@@ -41,7 +46,6 @@ func TestEndpointReadStream(t *testing.T) {
}
}()
ctx := NewContext()
dev, err := ctx.OpenDeviceWithVIDPID(0x9999, 0x0001)
if err != nil {
t.Fatalf("OpenDeviceWithVIDPID(9999, 0001): %v", err)

View File

@@ -20,9 +20,14 @@ import (
)
func TestEndpoint(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
lib, done := newFakeLibusb()
defer done()
t.Parallel()
lib := newFakeLibusb()
ctx := newContextWithImpl(lib)
defer func() {
if err := ctx.Close(); err != nil {
t.Errorf("Context.Close(): %v", err)
}
}()
for _, epData := range []struct {
ei EndpointDesc
@@ -92,7 +97,7 @@ func TestEndpoint(t *testing.T) {
wantErr: true,
},
} {
ep := &endpoint{h: nil, InterfaceSetting: epData.intf, Desc: epData.ei}
ep := &endpoint{h: nil, ctx: ctx, InterfaceSetting: epData.intf, Desc: epData.ei}
if tc.wantSubmit {
go func() {
fakeT := lib.waitForSubmitted()
@@ -163,12 +168,15 @@ func TestEndpointInfo(t *testing.T) {
}
func TestEndpointInOut(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
lib, done := newFakeLibusb()
defer done()
t.Parallel()
lib := newFakeLibusb()
ctx := newContextWithImpl(lib)
defer func() {
if err := ctx.Close(); err != nil {
t.Errorf("Context.Close(): %v", err)
}
}()
ctx := NewContext()
defer ctx.Close()
d, err := ctx.OpenDeviceWithVIDPID(0x9999, 0x0001)
if err != nil {
t.Fatalf("OpenDeviceWithVIDPID(0x9999, 0x0001): got error %v, want nil", err)
@@ -243,12 +251,14 @@ func TestEndpointInOut(t *testing.T) {
}
func TestSameEndpointNumberInOut(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()
t.Parallel()
ctx := newContextWithImpl(newFakeLibusb())
defer func() {
if err := ctx.Close(); err != nil {
t.Errorf("Context.Close(): %v", err)
}
}()
ctx := NewContext()
defer ctx.Close()
d, err := ctx.OpenDeviceWithVIDPID(0x1111, 0x1111)
if err != nil {
t.Fatalf("OpenDeviceWithVIDPID(0x1111, 0x1111): got error %v, want nil", err)

View File

@@ -64,9 +64,19 @@ func (f *fakeLibusb) getDevices(*libusbContext) ([]*libusbDevice, error) {
}
return ret, nil
}
func (f *fakeLibusb) exit(*libusbContext) {}
func (f *fakeLibusb) setDebug(*libusbContext, int) {}
func (f *fakeLibusb) exit(*libusbContext) error {
close(f.submitted)
if got := len(f.ts); got > 0 {
for t := range f.ts {
f.free(t)
}
return fmt.Errorf("fakeLibusb has %d remaining transfers that should have been freed", got)
}
return nil
}
func (f *fakeLibusb) setDebug(*libusbContext, int) {}
func (f *fakeLibusb) dereference(d *libusbDevice) {}
func (f *fakeLibusb) getDeviceDesc(d *libusbDevice) (*DeviceDesc, error) {
if dev, ok := f.fakeDevices[d]; ok {
@@ -191,8 +201,7 @@ func (f *fakeLibusb) empty() bool {
return len(f.submitted) == 0
}
func newFakeLibusb() (*fakeLibusb, func() error) {
origLibusb := libusb
func newFakeLibusb() *fakeLibusb {
fl := &fakeLibusb{
fakeDevices: make(map[*libusbDevice]*fakeDevice),
ts: make(map[*libusbTransfer]*fakeTransfer),
@@ -209,16 +218,5 @@ func newFakeLibusb() (*fakeLibusb, func() error) {
*fd = d
fl.fakeDevices[newDevicePointer()] = fd
}
libusb = fl
return fl, func() error {
defer func() { libusb = origLibusb }()
close(fl.submitted)
if got := len(fl.ts); got > 0 {
for t := range fl.ts {
fl.free(t)
}
return fmt.Errorf("fakeLibusb has %d remaining transfers that should have been freed", got)
}
return nil
}
return fl
}

View File

@@ -89,7 +89,7 @@ func (i *Interface) Close() {
if i.config == nil {
return
}
libusb.release(i.config.dev.handle, uint8(i.Setting.Number))
i.config.dev.ctx.libusb.release(i.config.dev.handle, uint8(i.Setting.Number))
i.config.mu.Lock()
defer i.config.mu.Unlock()
delete(i.config.claimed, i.Setting.Number)
@@ -106,6 +106,7 @@ func (i *Interface) openEndpoint(epAddr EndpointAddress) (*endpoint, error) {
InterfaceSetting: i.Setting,
Desc: ep,
h: i.config.dev.handle,
ctx: i.config.dev.ctx,
}, nil
}

View File

@@ -135,7 +135,7 @@ type libusbIntf interface {
init() (*libusbContext, error)
handleEvents(*libusbContext, <-chan struct{})
getDevices(*libusbContext) ([]*libusbDevice, error)
exit(*libusbContext)
exit(*libusbContext) error
setDebug(*libusbContext, int)
// device
@@ -213,8 +213,9 @@ func (libusbImpl) getDevices(ctx *libusbContext) ([]*libusbDevice, error) {
return ret, nil
}
func (libusbImpl) exit(c *libusbContext) {
func (libusbImpl) exit(c *libusbContext) error {
C.libusb_exit((*C.libusb_context)(c))
return nil
}
func (libusbImpl) setDebug(c *libusbContext, lvl int) {
@@ -479,9 +480,6 @@ func (libusbImpl) setIsoPacketLengths(t *libusbTransfer, length uint32) {
C.libusb_set_iso_packet_lengths((*C.struct_libusb_transfer)(t), C.uint(length))
}
// libusb is an injection point for tests
var libusb libusbIntf = libusbImpl{}
// xferDoneMap keeps a map of done callback channels for all allocated transfers.
var xferDoneMap = struct {
m map[*libusbTransfer]chan struct{}

View File

@@ -35,6 +35,8 @@ type usbTransfer struct {
done chan struct{}
// submitted is true if submit() was called on this transfer.
submitted bool
// ctx is the Context that created this transfer.
ctx *Context
}
// submits the transfer. After submit() the transfer is in flight and is owned by libusb.
@@ -46,7 +48,7 @@ func (t *usbTransfer) submit() error {
if t.submitted {
return errors.New("transfer was already submitted and is not finished yet")
}
if err := libusb.submit(t.xfer); err != nil {
if err := t.ctx.libusb.submit(t.xfer); err != nil {
return err
}
t.submitted = true
@@ -66,7 +68,7 @@ func (t *usbTransfer) wait() (n int, err error) {
}
<-t.done
t.submitted = false
n, status := libusb.data(t.xfer)
n, status := t.ctx.libusb.data(t.xfer)
if status != TransferCompleted {
return n, status
}
@@ -81,7 +83,7 @@ func (t *usbTransfer) cancel() error {
if !t.submitted {
return nil
}
err := libusb.cancel(t.xfer)
err := t.ctx.libusb.cancel(t.xfer)
if err == ErrorNotFound {
// transfer already completed
return nil
@@ -101,7 +103,7 @@ func (t *usbTransfer) free() error {
if t.xfer == nil {
return nil
}
libusb.free(t.xfer)
t.ctx.libusb.free(t.xfer)
t.xfer = nil
t.buf = nil
t.done = nil
@@ -115,7 +117,7 @@ func (t *usbTransfer) data() []byte {
// newUSBTransfer allocates a new transfer structure and a new buffer for
// communication with a given device/endpoint.
func newUSBTransfer(dev *libusbDevHandle, ei *EndpointDesc, bufLen int, timeout time.Duration) (*usbTransfer, error) {
func newUSBTransfer(ctx *Context, dev *libusbDevHandle, ei *EndpointDesc, bufLen int, timeout time.Duration) (*usbTransfer, error) {
var isoPackets, isoPktSize int
if ei.TransferType == TransferTypeIsochronous {
isoPktSize = ei.MaxPacketSize
@@ -127,19 +129,20 @@ func newUSBTransfer(dev *libusbDevHandle, ei *EndpointDesc, bufLen int, timeout
}
done := make(chan struct{}, 1)
xfer, err := libusb.alloc(dev, ei, timeout, isoPackets, bufLen, done)
xfer, err := ctx.libusb.alloc(dev, ei, timeout, isoPackets, bufLen, done)
if err != nil {
return nil, err
}
if ei.TransferType == TransferTypeIsochronous {
libusb.setIsoPacketLengths(xfer, uint32(isoPktSize))
ctx.libusb.setIsoPacketLengths(xfer, uint32(isoPktSize))
}
t := &usbTransfer{
xfer: xfer,
buf: libusb.buffer(xfer),
buf: ctx.libusb.buffer(xfer),
done: done,
ctx: ctx,
}
runtime.SetFinalizer(t, func(t *usbTransfer) {
t.cancel()

View File

@@ -20,9 +20,13 @@ import (
)
func TestNewTransfer(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()
t.Parallel()
ctx := newContextWithImpl(newFakeLibusb())
defer func() {
if err := ctx.Close(); err != nil {
t.Errorf("Context.Close(): %v", err)
}
}()
for _, tc := range []struct {
desc string
@@ -53,7 +57,7 @@ func TestNewTransfer(t *testing.T) {
wantLength: 10000,
},
} {
xfer, err := newUSBTransfer(nil, &EndpointDesc{
xfer, err := newUSBTransfer(ctx, nil, &EndpointDesc{
Number: 2,
Direction: tc.dir,
TransferType: tc.tt,
@@ -71,14 +75,19 @@ func TestNewTransfer(t *testing.T) {
}
func TestTransferProtocol(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
f, done := newFakeLibusb()
defer done()
t.Parallel()
f := newFakeLibusb()
ctx := newContextWithImpl(f)
defer func() {
if err := ctx.Close(); err != nil {
t.Errorf("Context.Close(): %v", err)
}
}()
xfers := make([]*usbTransfer, 2)
var err error
for i := 0; i < 2; i++ {
xfers[i], err = newUSBTransfer(nil, &EndpointDesc{
xfers[i], err = newUSBTransfer(ctx, nil, &EndpointDesc{
Number: 6,
Direction: EndpointDirectionIn,
TransferType: TransferTypeBulk,

35
usb.go
View File

@@ -129,35 +129,41 @@ package gousb
type Context struct {
ctx *libusbContext
done chan struct{}
libusb libusbIntf
}
// Debug changes the debug level. Level 0 means no debug, higher levels
// will print out more debugging information.
func (c *Context) Debug(level int) {
libusb.setDebug(c.ctx, level)
c.libusb.setDebug(c.ctx, level)
}
// NewContext returns a new Context instance.
func NewContext() *Context {
c, err := libusb.init()
func newContextWithImpl(impl libusbIntf) *Context {
c, err := impl.init()
if err != nil {
panic(err)
}
ctx := &Context{
ctx: c,
done: make(chan struct{}),
libusb: impl,
}
go libusb.handleEvents(ctx.ctx, ctx.done)
go impl.handleEvents(ctx.ctx, ctx.done)
return ctx
}
// NewContext returns a new Context instance.
func NewContext() *Context {
return newContextWithImpl(libusbImpl{})
}
// OpenDevices calls opener with each enumerated device.
// If the opener returns true, the device is opened and a Device is returned if the operation succeeds.
// Every Device returned (whether an error is also returned or not) must be closed.
// If there are any errors enumerating the devices,
// the final one is returned along with any successfully opened devices.
func (c *Context) OpenDevices(opener func(desc *DeviceDesc) bool) ([]*Device, error) {
list, err := libusb.getDevices(c.ctx)
list, err := c.libusb.getDevices(c.ctx)
if err != nil {
return nil, err
}
@@ -165,23 +171,23 @@ func (c *Context) OpenDevices(opener func(desc *DeviceDesc) bool) ([]*Device, er
var reterr error
var ret []*Device
for _, dev := range list {
desc, err := libusb.getDeviceDesc(dev)
desc, err := c.libusb.getDeviceDesc(dev)
if err != nil {
libusb.dereference(dev)
c.libusb.dereference(dev)
reterr = err
continue
}
if opener(desc) {
handle, err := libusb.open(dev)
handle, err := c.libusb.open(dev)
if err != nil {
libusb.dereference(dev)
c.libusb.dereference(dev)
reterr = err
continue
}
ret = append(ret, &Device{handle: handle, Desc: desc})
ret = append(ret, &Device{handle: handle, ctx: c, Desc: desc})
} else {
libusb.dereference(dev)
c.libusb.dereference(dev)
}
}
return ret, reterr
@@ -213,10 +219,11 @@ func (c *Context) OpenDeviceWithVIDPID(vid, pid ID) (*Device, error) {
// Close releases the Context and all associated resources.
func (c *Context) Close() error {
var ret error
c.done <- struct{}{}
if c.ctx != nil {
libusb.exit(c.ctx)
ret = c.libusb.exit(c.ctx)
}
c.ctx = nil
return nil
return ret
}

View File

@@ -18,12 +18,13 @@ package gousb
import "testing"
func TestOPenDevices(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()
c := NewContext()
defer c.Close()
t.Parallel()
c := newContextWithImpl(newFakeLibusb())
defer func() {
if err := c.Close(); err != nil {
t.Errorf("Context.Close(): %v", err)
}
}()
c.Debug(0)
descs := []*DeviceDesc{}
@@ -55,9 +56,13 @@ func TestOPenDevices(t *testing.T) {
}
func TestOpenDeviceWithVIDPID(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()
t.Parallel()
ctx := newContextWithImpl(newFakeLibusb())
defer func() {
if err := ctx.Close(); err != nil {
t.Errorf("Context.Close(): %v", err)
}
}()
for _, d := range []struct {
vid, pid ID
@@ -69,9 +74,7 @@ func TestOpenDeviceWithVIDPID(t *testing.T) {
{0x9999, 0x0001, true},
{0x9999, 0x0002, false},
} {
c := NewContext()
defer c.Close()
dev, err := c.OpenDeviceWithVIDPID(d.vid, d.pid)
dev, err := ctx.OpenDeviceWithVIDPID(d.vid, d.pid)
if (dev != nil) != d.exists {
t.Errorf("OpenDeviceWithVIDPID(%s/%s): device != nil is %v, want %v", ID(d.vid), ID(d.pid), dev != nil, d.exists)
}