2
0
mirror of synced 2025-02-24 14:48:27 +00:00
torrent/fs/torrentfs_test.go
Matt Joiner 2a7352aad2 Don't block trying to fill entire Reader.Read
This conforms more to the contract in io.Reader. It's possible the old behaviour was better in reducing overhead, but that can be iterated on (or added as comments next time).
2020-10-27 12:59:07 +11:00

245 lines
5.9 KiB
Go

package torrentfs
import (
"context"
netContext "context"
"fmt"
"io/ioutil"
"log"
"net"
_ "net/http/pprof"
"os"
"path/filepath"
"testing"
"time"
"bazil.org/fuse"
fusefs "bazil.org/fuse/fs"
_ "github.com/anacrolix/envpprof"
"github.com/anacrolix/missinggo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/internal/testutil"
"github.com/anacrolix/torrent/metainfo"
"github.com/anacrolix/torrent/storage"
)
func init() {
log.SetFlags(log.Flags() | log.Lshortfile)
}
func TestTCPAddrString(t *testing.T) {
l, err := net.Listen("tcp4", "localhost:0")
if err != nil {
t.Fatal(err)
}
defer l.Close()
c, err := net.Dial("tcp", l.Addr().String())
if err != nil {
t.Fatal(err)
}
defer c.Close()
ras := c.RemoteAddr().String()
ta := &net.TCPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: missinggo.AddrPort(l.Addr()),
}
s := ta.String()
if ras != s {
t.FailNow()
}
}
type testLayout struct {
BaseDir string
MountDir string
Completed string
Metainfo *metainfo.MetaInfo
}
func (tl *testLayout) Destroy() error {
return os.RemoveAll(tl.BaseDir)
}
func newGreetingLayout() (tl testLayout, err error) {
tl.BaseDir, err = ioutil.TempDir("", "torrentfs")
if err != nil {
return
}
tl.Completed = filepath.Join(tl.BaseDir, "completed")
os.Mkdir(tl.Completed, 0777)
tl.MountDir = filepath.Join(tl.BaseDir, "mnt")
os.Mkdir(tl.MountDir, 0777)
testutil.CreateDummyTorrentData(tl.Completed)
tl.Metainfo = testutil.GreetingMetaInfo()
return
}
// Unmount without first killing the FUSE connection while there are FUSE
// operations blocked inside the filesystem code.
func TestUnmountWedged(t *testing.T) {
layout, err := newGreetingLayout()
require.NoError(t, err)
defer func() {
err := layout.Destroy()
if err != nil {
t.Log(err)
}
}()
cfg := torrent.NewDefaultClientConfig()
cfg.DataDir = filepath.Join(layout.BaseDir, "incomplete")
cfg.DisableTrackers = true
cfg.NoDHT = true
cfg.DisableTCP = true
cfg.DisableUTP = true
client, err := torrent.NewClient(cfg)
require.NoError(t, err)
defer client.Close()
tt, err := client.AddTorrent(layout.Metainfo)
require.NoError(t, err)
fs := New(client)
fuseConn, err := fuse.Mount(layout.MountDir)
if err != nil {
switch err.Error() {
case "cannot locate OSXFUSE":
fallthrough
case "fusermount: exit status 1":
t.Skip(err)
}
t.Fatal(err)
}
go func() {
server := fusefs.New(fuseConn, &fusefs.Config{
Debug: func(msg interface{}) {
t.Log(msg)
},
})
server.Serve(fs)
}()
<-fuseConn.Ready
if err := fuseConn.MountError; err != nil {
t.Fatalf("mount error: %s", err)
}
ctx, cancel := context.WithCancel(context.Background())
// Read the greeting file, though it will never be available. This should
// "wedge" FUSE, requiring the fs object to be forcibly destroyed. The
// read call will return with a FS error.
go func() {
<-ctx.Done()
fs.mu.Lock()
fs.event.Broadcast()
fs.mu.Unlock()
}()
go func() {
defer cancel()
_, err := ioutil.ReadFile(filepath.Join(layout.MountDir, tt.Info().Name))
require.Error(t, err)
}()
// Wait until the read has blocked inside the filesystem code.
fs.mu.Lock()
for fs.blockedReads != 1 && ctx.Err() == nil {
fs.event.Wait()
}
fs.mu.Unlock()
fs.Destroy()
for {
err = fuse.Unmount(layout.MountDir)
if err != nil {
t.Logf("error unmounting: %s", err)
time.Sleep(time.Millisecond)
} else {
break
}
}
err = fuseConn.Close()
assert.NoError(t, err)
}
func TestDownloadOnDemand(t *testing.T) {
layout, err := newGreetingLayout()
require.NoError(t, err)
defer layout.Destroy()
cfg := torrent.NewDefaultClientConfig()
cfg.DataDir = layout.Completed
cfg.DisableTrackers = true
cfg.NoDHT = true
cfg.Seed = true
cfg.ListenPort = 0
cfg.ListenHost = torrent.LoopbackListenHost
seeder, err := torrent.NewClient(cfg)
require.NoError(t, err)
defer seeder.Close()
defer testutil.ExportStatusWriter(seeder, "s", t)()
// Just to mix things up, the seeder starts with the data, but the leecher
// starts with the metainfo.
seederTorrent, err := seeder.AddMagnet(fmt.Sprintf("magnet:?xt=urn:btih:%s", layout.Metainfo.HashInfoBytes().HexString()))
require.NoError(t, err)
go func() {
// Wait until we get the metainfo, then check for the data.
<-seederTorrent.GotInfo()
seederTorrent.VerifyData()
}()
cfg = torrent.NewDefaultClientConfig()
cfg.DisableTrackers = true
cfg.NoDHT = true
cfg.DisableTCP = true
cfg.DefaultStorage = storage.NewMMap(filepath.Join(layout.BaseDir, "download"))
cfg.ListenHost = torrent.LoopbackListenHost
cfg.ListenPort = 0
leecher, err := torrent.NewClient(cfg)
require.NoError(t, err)
testutil.ExportStatusWriter(leecher, "l", t)()
defer leecher.Close()
leecherTorrent, err := leecher.AddTorrent(layout.Metainfo)
require.NoError(t, err)
leecherTorrent.AddClientPeer(seeder)
fs := New(leecher)
defer fs.Destroy()
root, _ := fs.Root()
node, _ := root.(fusefs.NodeStringLookuper).Lookup(netContext.Background(), "greeting")
var attr fuse.Attr
node.Attr(netContext.Background(), &attr)
size := attr.Size
data := make([]byte, size)
h, err := node.(fusefs.NodeOpener).Open(context.TODO(), nil, nil)
require.NoError(t, err)
// torrent.Reader.Read no longer tries to fill the entire read buffer, so this is a ReadFull for
// fusefs.
var n int
for n < len(data) {
resp := fuse.ReadResponse{Data: data[n:]}
err := h.(fusefs.HandleReader).Read(netContext.Background(), &fuse.ReadRequest{
Size: int(size) - n,
Offset: int64(n),
}, &resp)
assert.NoError(t, err)
n += len(resp.Data)
}
assert.EqualValues(t, testutil.GreetingFileContents, data)
}
func TestIsSubPath(t *testing.T) {
for _, case_ := range []struct {
parent, child string
is bool
}{
{"", "", false},
{"", "/", true},
{"", "a", true},
{"a/b", "a/bc", false},
{"a/b", "a/b", false},
{"a/b", "a/b/c", true},
{"a/b", "a//b", false},
} {
assert.Equal(t, case_.is, isSubPath(case_.parent, case_.child))
}
}