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 - darwin
- windows - 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. 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 [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 // 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) 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 { if err := c.dev.ctx.libusb.setAlt(c.dev.handle, uint8(num), uint8(alt)); err != nil {
libusb.release(c.dev.handle, uint8(num)) 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) 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. // A Device must be Close()d after use.
type Device struct { type Device struct {
handle *libusbDevHandle handle *libusbDevHandle
ctx *Context
// Embed the device information for easy access // Embed the device information for easy access
Desc *DeviceDesc Desc *DeviceDesc
@@ -109,7 +110,7 @@ func (d *Device) Reset() error {
if d.claimed != nil { if d.claimed != nil {
return fmt.Errorf("can't reset device %s while it has an active configuration %s", d, d.claimed) 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. // ActiveConfigNum returns the config id of the active configuration.
@@ -119,7 +120,7 @@ func (d *Device) ActiveConfigNum() (int, error) {
if d.handle == nil { if d.handle == nil {
return 0, fmt.Errorf("ActiveConfig() called on %s after Close", d) 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 return int(ret), err
} }
@@ -146,7 +147,7 @@ func (d *Device) Config(cfgNum int) (*Config, error) {
if d.autodetach { if d.autodetach {
for _, iface := range cfg.Desc.Interfaces { 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) 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 { if activeCfgNum, err := d.ActiveConfigNum(); err != nil {
return nil, fmt.Errorf("failed to query active config of the device %s: %v", d, err) return nil, fmt.Errorf("failed to query active config of the device %s: %v", d, err)
} else if cfgNum != activeCfgNum { } 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) 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 { if d.handle == nil {
return 0, fmt.Errorf("Control() called on %s after Close", d) 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. // Close closes the device.
@@ -207,7 +208,7 @@ func (d *Device) Close() error {
if d.claimed != nil { if d.claimed != nil {
return fmt.Errorf("can't release the device %s, it has an open config %d", d, d.claimed.Desc.Number) 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 d.handle = nil
return nil return nil
} }
@@ -224,7 +225,7 @@ func (d *Device) GetStringDescriptor(descIndex int) (string, error) {
if descIndex == 0 { if descIndex == 0 {
return "", nil return "", nil
} }
return libusb.getStringDesc(d.handle, descIndex) return d.ctx.libusb.getStringDesc(d.handle, descIndex)
} }
// Manufacturer returns the device's manufacturer name. // Manufacturer returns the device's manufacturer name.
@@ -283,5 +284,5 @@ func (d *Device) SetAutoDetach(autodetach bool) error {
if autodetach { if autodetach {
autodetachInt = 1 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) { func TestClaimAndRelease(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state. t.Parallel()
_, done := newFakeLibusb()
defer done()
const ( const (
devIdx = 1 devIdx = 1
cfgNum = 1 cfgNum = 1
@@ -34,8 +31,14 @@ func TestClaimAndRelease(t *testing.T) {
alt2Num = 0 alt2Num = 0
if2Num = 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) dev, err := c.OpenDeviceWithVIDPID(0x8888, 0x0002)
if dev == nil { if dev == nil {
t.Fatal("OpenDeviceWithVIDPID(0x8888, 0x0002): got nil device, need non-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) { func TestInterfaceDescriptionError(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state. t.Parallel()
_, done := newFakeLibusb()
defer done()
for _, tc := range []struct { for _, tc := range []struct {
name string name string
cfg, intf, alt int cfg, intf, alt int
@@ -189,10 +189,15 @@ func TestInterfaceDescriptionError(t *testing.T) {
{"no interface", 1, 3, 1}, {"no interface", 1, 3, 1},
{"no alt setting", 1, 1, 5}, {"no alt setting", 1, 1, 5},
} { } {
tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
// Can't be parallelized, depends on the shared global state set before the loop. t.Parallel()
c := NewContext() c := newContextWithImpl(newFakeLibusb())
defer c.Close() defer func() {
if err := c.Close(); err != nil {
t.Errorf("Context.Close(): %v", err)
}
}()
dev, err := c.OpenDeviceWithVIDPID(0x8888, 0x0002) dev, err := c.OpenDeviceWithVIDPID(0x8888, 0x0002)
if dev == nil { if dev == nil {
t.Fatal("OpenDeviceWithVIDPID(0x8888, 0x0002): got nil device, need non-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) { func TestAutoDetachFailure(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state. t.Parallel()
fake, done := newFakeLibusb() fake := newFakeLibusb()
defer done() c := newContextWithImpl(&failDetachLib{fake})
libusb = &failDetachLib{fake}
c := NewContext()
defer c.Close() defer c.Close()
dev, err := c.OpenDeviceWithVIDPID(0x8888, 0x0002) dev, err := c.OpenDeviceWithVIDPID(0x8888, 0x0002)
if dev == nil { if dev == nil {

View File

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

View File

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

View File

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

View File

@@ -20,9 +20,14 @@ import (
) )
func TestEndpoint(t *testing.T) { func TestEndpoint(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state. t.Parallel()
lib, done := newFakeLibusb() lib := newFakeLibusb()
defer done() ctx := newContextWithImpl(lib)
defer func() {
if err := ctx.Close(); err != nil {
t.Errorf("Context.Close(): %v", err)
}
}()
for _, epData := range []struct { for _, epData := range []struct {
ei EndpointDesc ei EndpointDesc
@@ -92,7 +97,7 @@ func TestEndpoint(t *testing.T) {
wantErr: true, 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 { if tc.wantSubmit {
go func() { go func() {
fakeT := lib.waitForSubmitted() fakeT := lib.waitForSubmitted()
@@ -163,12 +168,15 @@ func TestEndpointInfo(t *testing.T) {
} }
func TestEndpointInOut(t *testing.T) { func TestEndpointInOut(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state. t.Parallel()
lib, done := newFakeLibusb() lib := newFakeLibusb()
defer done() 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) d, err := ctx.OpenDeviceWithVIDPID(0x9999, 0x0001)
if err != nil { if err != nil {
t.Fatalf("OpenDeviceWithVIDPID(0x9999, 0x0001): got error %v, want nil", err) 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) { func TestSameEndpointNumberInOut(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state. t.Parallel()
_, done := newFakeLibusb() ctx := newContextWithImpl(newFakeLibusb())
defer done() 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) d, err := ctx.OpenDeviceWithVIDPID(0x1111, 0x1111)
if err != nil { if err != nil {
t.Fatalf("OpenDeviceWithVIDPID(0x1111, 0x1111): got error %v, want nil", err) 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 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) dereference(d *libusbDevice) {}
func (f *fakeLibusb) getDeviceDesc(d *libusbDevice) (*DeviceDesc, error) { func (f *fakeLibusb) getDeviceDesc(d *libusbDevice) (*DeviceDesc, error) {
if dev, ok := f.fakeDevices[d]; ok { if dev, ok := f.fakeDevices[d]; ok {
@@ -191,8 +201,7 @@ func (f *fakeLibusb) empty() bool {
return len(f.submitted) == 0 return len(f.submitted) == 0
} }
func newFakeLibusb() (*fakeLibusb, func() error) { func newFakeLibusb() *fakeLibusb {
origLibusb := libusb
fl := &fakeLibusb{ fl := &fakeLibusb{
fakeDevices: make(map[*libusbDevice]*fakeDevice), fakeDevices: make(map[*libusbDevice]*fakeDevice),
ts: make(map[*libusbTransfer]*fakeTransfer), ts: make(map[*libusbTransfer]*fakeTransfer),
@@ -209,16 +218,5 @@ func newFakeLibusb() (*fakeLibusb, func() error) {
*fd = d *fd = d
fl.fakeDevices[newDevicePointer()] = fd fl.fakeDevices[newDevicePointer()] = fd
} }
libusb = fl return 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
}
} }

View File

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

View File

@@ -135,7 +135,7 @@ type libusbIntf interface {
init() (*libusbContext, error) init() (*libusbContext, error)
handleEvents(*libusbContext, <-chan struct{}) handleEvents(*libusbContext, <-chan struct{})
getDevices(*libusbContext) ([]*libusbDevice, error) getDevices(*libusbContext) ([]*libusbDevice, error)
exit(*libusbContext) exit(*libusbContext) error
setDebug(*libusbContext, int) setDebug(*libusbContext, int)
// device // device
@@ -213,8 +213,9 @@ func (libusbImpl) getDevices(ctx *libusbContext) ([]*libusbDevice, error) {
return ret, nil return ret, nil
} }
func (libusbImpl) exit(c *libusbContext) { func (libusbImpl) exit(c *libusbContext) error {
C.libusb_exit((*C.libusb_context)(c)) C.libusb_exit((*C.libusb_context)(c))
return nil
} }
func (libusbImpl) setDebug(c *libusbContext, lvl int) { 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)) 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. // xferDoneMap keeps a map of done callback channels for all allocated transfers.
var xferDoneMap = struct { var xferDoneMap = struct {
m map[*libusbTransfer]chan struct{} m map[*libusbTransfer]chan struct{}

View File

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

View File

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

35
usb.go
View File

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

View File

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