Skip to content

Commit

Permalink
feat: remove unixfs-preload use
Browse files Browse the repository at this point in the history
  • Loading branch information
rvagg committed Jun 27, 2023
1 parent 0f522b7 commit 00b54cc
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 79 deletions.
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/ipfs/go-ipfs-delay v0.0.1
github.com/ipfs/go-ipfs-exchange-interface v0.2.0
github.com/ipfs/go-ipld-format v0.5.0
github.com/ipfs/go-libipfs v0.6.0
github.com/ipfs/go-log/v2 v2.5.1
github.com/ipfs/go-unixfsnode v1.7.1
github.com/ipld/go-car/v2 v2.10.1
Expand Down Expand Up @@ -166,3 +167,7 @@ require (
lukechampine.com/blake3 v1.1.7 // indirect
nhooyr.io/websocket v1.8.7 // indirect
)

replace github.com/ipfs/go-unixfsnode => ../../ipfs/go-unixfsnode

replace github.com/ipld/go-ipld-prime => ../../ipld/go-ipld-prime
12 changes: 5 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
Expand Down Expand Up @@ -292,10 +292,11 @@ github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNo
github.com/ipfs/go-ipfs-exchange-interface v0.2.0 h1:8lMSJmKogZYNo2jjhUs0izT+dck05pqUw4mWNW9Pw6Y=
github.com/ipfs/go-ipfs-exchange-interface v0.2.0/go.mod h1:z6+RhJuDQbqKguVyslSOuVDhqF9JtTrO3eptSAiW2/Y=
github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA=
github.com/ipfs/go-ipfs-files v0.0.8 h1:8o0oFJkJ8UkO/ABl8T6ac6tKF3+NIpj67aAB6ZpusRg=
github.com/ipfs/go-ipfs-files v0.3.0 h1:fallckyc5PYjuMEitPNrjRfpwl7YFt69heCOUhsbGxQ=
github.com/ipfs/go-ipfs-posinfo v0.0.1 h1:Esoxj+1JgSjX0+ylc0hUmJCOv6V2vFoZiETLR6OtpRs=
github.com/ipfs/go-ipfs-pq v0.0.3 h1:YpoHVJB+jzK15mr/xsWC574tyDLkezVrDNeaalQBsTE=
github.com/ipfs/go-ipfs-pq v0.0.3/go.mod h1:btNw5hsHBpRcSSgZtiNm/SLj5gYIZ18AKtv3kERkRb4=
github.com/ipfs/go-ipfs-routing v0.3.0 h1:9W/W3N+g+y4ZDeffSgqhgo7BsBSJwPMcyssET9OWevc=
github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc=
github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8=
github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ=
Expand All @@ -311,6 +312,7 @@ github.com/ipfs/go-ipld-format v0.5.0 h1:WyEle9K96MSrvr47zZHKKcDxJ/vlpET6PSiQsAF
github.com/ipfs/go-ipld-format v0.5.0/go.mod h1:ImdZqJQaEouMjCvqCe0ORUS+uoBmf7Hf+EO/jh+nk3M=
github.com/ipfs/go-ipld-legacy v0.2.1 h1:mDFtrBpmU7b//LzLSypVrXsD8QxkEWxu5qVxN99/+tk=
github.com/ipfs/go-libipfs v0.6.0 h1:3FuckAJEm+zdHbHbf6lAyk0QUzc45LsFcGw102oBCZM=
github.com/ipfs/go-libipfs v0.6.0/go.mod h1:UjjDIuehp2GzlNP0HEr5I9GfFT7zWgst+YfpUEIThtw=
github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
github.com/ipfs/go-log v1.0.0/go.mod h1:JO7RzlMK6rA+CIxFMLOuB6Wf5b81GDiKElL7UPSIKjA=
github.com/ipfs/go-log v1.0.1/go.mod h1:HuWlQttfN6FWNHRhlY5yMk/lW7evQC0HHGOxEwMRR8I=
Expand All @@ -329,15 +331,11 @@ github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j
github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg=
github.com/ipfs/go-peertaskqueue v0.8.1/go.mod h1:Oxxd3eaK279FxeydSPPVGHzbwVeHjatZ2GA8XD+KbPU=
github.com/ipfs/go-unixfs v0.4.5 h1:wj8JhxvV1G6CD7swACwSKYa+NgtdWC1RUit+gFnymDU=
github.com/ipfs/go-unixfsnode v1.7.1 h1:RRxO2b6CSr5UQ/kxnGzaChTjp5LWTdf3Y4n8ANZgB/s=
github.com/ipfs/go-unixfsnode v1.7.1/go.mod h1:PVfoyZkX1B34qzT3vJO4nsLUpRCyhnMuHBznRcXirlk=
github.com/ipfs/go-verifcid v0.0.2 h1:XPnUv0XmdH+ZIhLGKg6U2vaPaRDXb9urMyNVCE7uvTs=
github.com/ipld/go-car/v2 v2.10.1 h1:MRDqkONNW9WRhB79u+Z3U5b+NoN7lYA5B8n8qI3+BoI=
github.com/ipld/go-car/v2 v2.10.1/go.mod h1:sQEkXVM3csejlb1kCCb+vQ/pWBKX9QtvsrysMQjOgOg=
github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc=
github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s=
github.com/ipld/go-ipld-prime v0.20.1-0.20230329011551-5056175565b0 h1:iJTl9tx5DEsnKpppX5PmfdoQ3ITuBmkh3yyEpHWY2SI=
github.com/ipld/go-ipld-prime v0.20.1-0.20230329011551-5056175565b0/go.mod h1:wmOtdy70ajP48iZITH8uLsGJVMqA4EJM61/bSfYYGhs=
github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo=
github.com/ipni/go-libipni v0.0.8-0.20230425184153-86a1fcb7f7ff h1:xbKrIvnpQkbF8iHPk/HGcegsypCDpcXWHhzBCLyCWf8=
github.com/ipni/go-libipni v0.0.8-0.20230425184153-86a1fcb7f7ff/go.mod h1:paYP9U4N3/vOzGCuN9kU972vtvw9JUcQjOKyiCFGwRk=
Expand Down Expand Up @@ -616,7 +614,7 @@ github.com/urfave/cli/v2 v2.24.4 h1:0gyJJEBYtCV87zI/x2nZCPyDxD51K6xM8SkwjHFCNEU=
github.com/urfave/cli/v2 v2.24.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/warpfork/go-testmark v0.11.0 h1:J6LnV8KpceDvo7spaNU4+DauH2n1x+6RaO2rJrmpQ9U=
github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s=
github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
github.com/warpfork/go-wish v0.0.0-20190328234359-8b3e70f8e830/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
Expand Down
4 changes: 1 addition & 3 deletions pkg/internal/itest/testpeer/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ func MockIpfsHandler(ctx context.Context, lsys linking.LinkSystem) func(http.Res
}
progress := traversal.Progress{Cfg: cfg}

err = progress.WalkAdv(rootNode, sel, visitNoop)
err = progress.WalkMatching(rootNode, sel, unixfsnode.BytesConsumingMatcher)
if err != nil {
// if we loaded the first block, we can't write headers any more
return
Expand Down Expand Up @@ -482,5 +482,3 @@ func RandTestPeerIdentity() (tnet.Identity, error) {
}
return nil, errors.New("failed to find an available port")
}

func visitNoop(p traversal.Progress, n datamodel.Node, vr traversal.VisitReason) error { return nil }
3 changes: 1 addition & 2 deletions pkg/internal/testutil/toblocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ func ToBlocks(t *testing.T, lsys linking.LinkSystem, root cid.Cid, selNode datam
LinkTargetNodePrototypeChooser: dagpb.AddSupportToChooser(basicnode.Chooser),
},
}
vf := func(p traversal.Progress, n datamodel.Node, vr traversal.VisitReason) error { return nil }
err = prog.WalkAdv(rootNode, sel, vf)
err = prog.WalkMatching(rootNode, sel, unixfsnode.BytesConsumingMatcher)
require.NoError(t, err)

return traversedBlocks
Expand Down
5 changes: 4 additions & 1 deletion pkg/retriever/bitswapretriever.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import (
"github.com/ipfs/boxo/bitswap/network"
"github.com/ipfs/boxo/blockservice"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-libipfs/bitswap/client"
"github.com/ipfs/go-libipfs/bitswap/network"
"github.com/ipfs/go-unixfsnode"
dagpb "github.com/ipld/go-codec-dagpb"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/linking"
Expand Down Expand Up @@ -342,5 +345,5 @@ func easyTraverse(
if err != nil {
return err
}
return progress.WalkAdv(node, compiledSelector, func(prog traversal.Progress, n datamodel.Node, reason traversal.VisitReason) error { return nil })
return progress.WalkMatching(node, compiledSelector, unixfsnode.BytesConsumingMatcher)
}
157 changes: 94 additions & 63 deletions pkg/verifiedcar/verifiedcar.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ func (cfg Config) VerifyCar(ctx context.Context, rdr io.Reader, lsys linking.Lin
}

func (cfg Config) VerifyBlockStream(ctx context.Context, cbr BlockReader, lsys linking.LinkSystem) (uint64, uint64, error) {

sel, err := selector.CompileSelector(cfg.Selector)
if err != nil {
return 0, 0, err
Expand All @@ -104,7 +103,8 @@ func (cfg Config) VerifyBlockStream(ctx context.Context, cbr BlockReader, lsys l
lsys.TrustedStorage = true // we can rely on the CAR decoder to check CID integrity
unixfsnode.AddUnixFSReificationToLinkSystem(&lsys)

lsys.StorageReadOpener = cfg.nextBlockReadOpener(ctx, cr, bt, lsys)
vro := newVerifyingReadOpener(ctx, cfg, cr, bt, lsys)
lsys.StorageReadOpener = vro.StorageReadOpener

// run traversal in this goroutine
progress := traversal.Progress{
Expand All @@ -120,27 +120,13 @@ func (cfg Config) VerifyBlockStream(ctx context.Context, cbr BlockReader, lsys l
NodeBudget: math.MaxInt64,
}
}
lc := linking.LinkContext{Ctx: ctx}
lnk := cidlink.Link{Cid: cfg.Root}
proto, err := protoChooser(lnk, lc)
if err != nil {
return 0, 0, err
}
rootNode, err := lsys.Load(lc, lnk, proto)

rootNode, err := loadNode(ctx, cfg.Root, lsys)
if err != nil {
return 0, 0, err
return 0, 0, fmt.Errorf("failed to load root node: %w", err)
}
if err := progress.WalkMatching(rootNode, sel, func(p traversal.Progress, n datamodel.Node) error {
if lbn, ok := n.(datamodel.LargeBytesNode); ok {
rdr, err := lbn.AsLargeBytes()
if err != nil {
return err
}
_, err = io.Copy(io.Discard, rdr)
return err
}
return nil
}); err != nil {
// if err := progress.WalkAdv(rootNode, sel, visitNoop); err != nil {
if err := progress.WalkMatching(rootNode, sel, unixfsnode.BytesConsumingMatcher); err != nil {
return 0, 0, traversalError(err)
}

Expand All @@ -150,62 +136,107 @@ func (cfg Config) VerifyBlockStream(ctx context.Context, cbr BlockReader, lsys l
return 0, 0, ErrExtraneousBlock
}

if vro.err != nil {
return 0, 0, fmt.Errorf("block load failed during traversal: %w", vro.err)
}

// wait for parser to finish and provide errors or stats
return bt.blocks, bt.bytes, nil
}

func (cfg *Config) nextBlockReadOpener(ctx context.Context, cr *carReader, bt *writeTracker, lsys linking.LinkSystem) linking.BlockReadOpener {
seen := make(map[cid.Cid]struct{})
return func(lc linking.LinkContext, l datamodel.Link) (io.Reader, error) {
cid := l.(cidlink.Link).Cid

var data []byte
var err error
if _, ok := seen[cid]; ok {
if cfg.ExpectDuplicatesIn {
// duplicate block, but in this case we are expecting the stream to have it
data, err = cr.readNextBlock(ctx, cid)
if err != nil {
return nil, err
}
if !cfg.WriteDuplicatesOut {
return bytes.NewReader(data), nil
}
} else {
// duplicate block, rely on the supplied LinkSystem to have stored this
rdr, err := lsys.StorageReadOpener(lc, l)
if !cfg.WriteDuplicatesOut {
return rdr, err
}
data, err = io.ReadAll(rdr)
if err != nil {
return nil, err
}
func loadNode(ctx context.Context, rootCid cid.Cid, lsys linking.LinkSystem) (datamodel.Node, error) {
lnk := cidlink.Link{Cid: rootCid}
lnkCtx := linking.LinkContext{Ctx: ctx}
proto, err := protoChooser(lnk, lnkCtx)
if err != nil {
return nil, fmt.Errorf("failed to choose prototype for CID %s: %w", rootCid.String(), err)
}
rootNode, err := lsys.Load(lnkCtx, lnk, proto)
if err != nil {
return nil, fmt.Errorf("failed to load root CID: %w", err)
}
return rootNode, nil
}

type verifyingReadOpener struct {
ctx context.Context
cfg Config
cr *carReader
bt *writeTracker
lsys linking.LinkSystem

seen map[cid.Cid]struct{}
err error
}

func newVerifyingReadOpener(ctx context.Context, cfg Config, cr *carReader, bt *writeTracker, lsys linking.LinkSystem) *verifyingReadOpener {
return &verifyingReadOpener{
ctx: ctx,
cfg: cfg,
cr: cr,
bt: bt,
lsys: lsys,
seen: make(map[cid.Cid]struct{}),
}
}

func (vro *verifyingReadOpener) StorageReadOpener(lc linking.LinkContext, l datamodel.Link) (io.Reader, error) {
cid := l.(cidlink.Link).Cid

var data []byte
var err error
if _, ok := vro.seen[cid]; ok {
if vro.cfg.ExpectDuplicatesIn {
// duplicate block, but in this case we are expecting the stream to have it
data, err = vro.cr.readNextBlock(vro.ctx, cid)
if err != nil {
vro.err = err
return nil, err
}
if !vro.cfg.WriteDuplicatesOut {
return bytes.NewReader(data), nil
}
} else {
seen[cid] = struct{}{}
data, err = cr.readNextBlock(ctx, cid)
// duplicate block, rely on the supplied LinkSystem to have stored this
rdr, err := vro.lsys.StorageReadOpener(lc, l)
if !vro.cfg.WriteDuplicatesOut {
vro.err = err
return rdr, err
}
data, err = io.ReadAll(rdr)
if err != nil {
vro.err = err
return nil, err
}
}
bt.recordBlock(data)
w, wc, err := lsys.StorageWriteOpener(lc)
} else {
vro.seen[cid] = struct{}{}
data, err = vro.cr.readNextBlock(vro.ctx, cid)
if err != nil {
vro.err = err
return nil, err
}
rdr := bytes.NewReader(data)
if _, err := io.Copy(w, rdr); err != nil {
return nil, err
}
if err := wc(l); err != nil {
return nil, err
}
if _, err := rdr.Seek(0, io.SeekStart); err != nil {
return nil, err
}
return io.NopCloser(rdr), nil
}
vro.bt.recordBlock(data)
w, wc, err := vro.lsys.StorageWriteOpener(lc)
if err != nil {
vro.err = err
return nil, err
}
rdr := bytes.NewReader(data)
if _, err := io.Copy(w, rdr); err != nil {
vro.err = err
return nil, err
}
if err := wc(l); err != nil {
vro.err = err
return nil, err
}
if _, err := rdr.Seek(0, io.SeekStart); err != nil {
vro.err = err
return nil, err
}
return io.NopCloser(rdr), nil
}

type carReader struct {
Expand Down
22 changes: 19 additions & 3 deletions pkg/verifiedcar/verifiedcar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ipld/go-ipld-prime/storage/memstore"
"github.com/ipld/go-ipld-prime/traversal"
"github.com/ipld/go-ipld-prime/traversal/selector"
"github.com/ipld/go-ipld-prime/traversal/selector/builder"
selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -86,7 +87,13 @@ func TestVerifiedCar(t *testing.T) {
}
}

unixfsShardedDir := testutil.GenerateNoDupes(func() unixfs.DirEntry { return unixfs.GenerateDirectory(t, &lsys, rndReader, 8<<20, true) })
var unixfsShardedDir unixfs.DirEntry
for {
unixfsShardedDir = testutil.GenerateNoDupes(func() unixfs.DirEntry { return unixfs.GenerateDirectory(t, &lsys, rndReader, 8<<20, true) })
if len(unixfsShardedDir.SelfCids) >= 4 { // we want an _actual_ sharded directory
break
}
}
unixfsShardedDirBlocks := testutil.ToBlocks(t, lsys, unixfsShardedDir.Root, allSelector)

unixfsPreloadSelector := unixfsnode.MatchUnixFSPreloadSelector.Node()
Expand All @@ -98,6 +105,11 @@ func TestVerifiedCar(t *testing.T) {

unixfsWrappedPathSelector := unixfsnode.UnixFSPathSelectorBuilder(wrapPath, unixfsnode.ExploreAllRecursivelySelector, false)
unixfsWrappedPreloadPathSelector := unixfsnode.UnixFSPathSelectorBuilder(wrapPath, unixfsnode.MatchUnixFSPreloadSelector, false)
preloadSubst := ssb.ExploreInterpretAs("unixfs", ssb.ExploreRecursive(
selector.RecursionLimitDepth(1),
ssb.ExploreAll(ssb.ExploreRecursiveEdge()),
))
unixfsWrappedPreloadPathSelectorSubst := unixfsnode.UnixFSPathSelectorBuilder(wrapPath, preloadSubst, false)

unixfsWrappedFile := testutil.GenerateNoDupes(func() unixfs.DirEntry { return unixfs.WrapContent(t, rndReader, &lsys, unixfsFile, wrapPath, false) })
unixfsWrappedFileBlocks := testutil.ToBlocks(t, lsys, unixfsWrappedFile.Root, allSelector)
Expand All @@ -124,6 +136,7 @@ func TestVerifiedCar(t *testing.T) {
mismatchedCidBlk, _ := blocks.NewBlockWithCid(extraneousByts, allBlocks[99].Cid())
testCases := []struct {
name string
skip bool
blocks []expectedBlock
roots []cid.Cid
carv2 bool
Expand Down Expand Up @@ -304,7 +317,7 @@ func TestVerifiedCar(t *testing.T) {
name: "unixfs: all of large sharded directory with file scope, errors",
blocks: consumedBlocks(unixfsShardedDirBlocks),
roots: []cid.Cid{unixfsShardedDir.Root},
err: "extraneous block in CAR",
err: "unexpected block in CAR",
cfg: verifiedcar.Config{
Root: unixfsShardedDir.Root,
Selector: unixfsPreloadSelector,
Expand Down Expand Up @@ -449,7 +462,7 @@ func TestVerifiedCar(t *testing.T) {
roots: []cid.Cid{unixfsExclusiveWrappedShardedDir.Root},
cfg: verifiedcar.Config{
Root: unixfsExclusiveWrappedShardedDir.Root,
Selector: unixfsWrappedPreloadPathSelector,
Selector: unixfsWrappedPreloadPathSelectorSubst,
},
},
{
Expand Down Expand Up @@ -528,6 +541,9 @@ func TestVerifiedCar(t *testing.T) {
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
if testCase.skip {
t.Skip()
}
t.Parallel()

ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
Expand Down

0 comments on commit 00b54cc

Please sign in to comment.