]> Sergey Matveev's repositories - btrtrc.git/blobdiff - fs/torrentfs_test.go
Drop support for go 1.20
[btrtrc.git] / fs / torrentfs_test.go
index 20bea21b74ba1db892305f95fe5377f4803ef340..097f1bb2377449d1c4300e4d8496a1f90806ad11 100644 (file)
@@ -1,31 +1,28 @@
 package torrentfs
 
 import (
-       "bytes"
+       "context"
        "fmt"
        "io/ioutil"
        "log"
        "net"
-       "net/http"
        _ "net/http/pprof"
        "os"
        "path/filepath"
-       "strconv"
-       "strings"
        "testing"
        "time"
 
-       "bazil.org/fuse"
-       fusefs "bazil.org/fuse/fs"
-       "github.com/gorilla/context"
-       netContext "golang.org/x/net/context"
+       _ "github.com/anacrolix/envpprof"
+       "github.com/anacrolix/fuse"
+       fusefs "github.com/anacrolix/fuse/fs"
+       "github.com/anacrolix/missinggo/v2"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
 
        "github.com/anacrolix/torrent"
-       "github.com/anacrolix/torrent/data"
-       "github.com/anacrolix/torrent/data/mmap"
        "github.com/anacrolix/torrent/internal/testutil"
        "github.com/anacrolix/torrent/metainfo"
-       "github.com/anacrolix/torrent/util"
+       "github.com/anacrolix/torrent/storage"
 )
 
 func init() {
@@ -46,7 +43,7 @@ func TestTCPAddrString(t *testing.T) {
        ras := c.RemoteAddr().String()
        ta := &net.TCPAddr{
                IP:   net.IPv4(127, 0, 0, 1),
-               Port: util.AddrPort(l.Addr()),
+               Port: missinggo.AddrPort(l.Addr()),
        }
        s := ta.String()
        if ras != s {
@@ -61,83 +58,85 @@ type testLayout struct {
        Metainfo  *metainfo.MetaInfo
 }
 
-func (me *testLayout) Destroy() error {
-       return os.RemoveAll(me.BaseDir)
+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
-       }
+func newGreetingLayout(t *testing.T) (tl testLayout, err error) {
+       tl.BaseDir = t.TempDir()
        tl.Completed = filepath.Join(tl.BaseDir, "completed")
-       os.Mkdir(tl.Completed, 0777)
+       os.Mkdir(tl.Completed, 0o777)
        tl.MountDir = filepath.Join(tl.BaseDir, "mnt")
-       os.Mkdir(tl.MountDir, 0777)
-       name := testutil.CreateDummyTorrentData(tl.Completed)
-       metaInfoBuf := &bytes.Buffer{}
-       testutil.CreateMetaInfo(name, metaInfoBuf)
-       tl.Metainfo, err = metainfo.Load(metaInfoBuf)
+       os.Mkdir(tl.MountDir, 0o777)
+       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()
-       if err != nil {
-               t.Fatal(err)
-       }
+       layout, err := newGreetingLayout(t)
+       require.NoError(t, err)
        defer func() {
                err := layout.Destroy()
                if err != nil {
                        t.Log(err)
                }
        }()
-       client, err := torrent.NewClient(&torrent.Config{
-               DataDir:         filepath.Join(layout.BaseDir, "incomplete"),
-               DisableTrackers: true,
-               NoDHT:           true,
-
-               NoDefaultBlocklist: true,
-       })
+       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()
-       client.AddTorrent(layout.Metainfo)
+       tt, err := client.AddTorrent(layout.Metainfo)
+       require.NoError(t, err)
        fs := New(client)
        fuseConn, err := fuse.Mount(layout.MountDir)
        if err != nil {
-               msg := fmt.Sprintf("error mounting: %s", err)
-               if strings.Contains(err.Error(), "fuse") || err.Error() == "exit status 71" {
-                       t.Skip(msg)
+               switch err.Error() {
+               case "cannot locate OSXFUSE":
+                       fallthrough
+               case "fusermount: exit status 1":
+                       t.Skip(err)
                }
-               t.Fatal(msg)
+               t.Fatal(err)
        }
        go func() {
-               server := fusefs.Server{
-                       FS: fs,
+               server := fusefs.New(fuseConn, &fusefs.Config{
                        Debug: func(msg interface{}) {
                                t.Log(msg)
                        },
-               }
-               server.Serve(fuseConn)
+               })
+               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() {
-               _, err := ioutil.ReadFile(filepath.Join(layout.MountDir, layout.Metainfo.Info.Name))
-               if err == nil {
-                       t.Fatal("expected error reading greeting")
-               }
+               <-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 {
+       for fs.blockedReads != 1 && ctx.Err() == nil {
                fs.event.Wait()
        }
        fs.mu.Unlock()
@@ -155,96 +154,87 @@ func TestUnmountWedged(t *testing.T) {
        }
 
        err = fuseConn.Close()
-       if err != nil {
-               t.Fatalf("error closing fuse conn: %s", err)
-       }
+       assert.NoError(t, err)
 }
 
 func TestDownloadOnDemand(t *testing.T) {
-       layout, err := newGreetingLayout()
-       if err != nil {
-               t.Fatal(err)
-       }
+       layout, err := newGreetingLayout(t)
+       require.NoError(t, err)
        defer layout.Destroy()
-       seeder, err := torrent.NewClient(&torrent.Config{
-               DataDir:         layout.Completed,
-               DisableTrackers: true,
-               NoDHT:           true,
-               ListenAddr:      ":0",
-
-               NoDefaultBlocklist: true,
-               // Ensure that the metainfo is obtained over the wire, since we added
-               // the torrent to the seeder by magnet.
-               DisableMetainfoCache: true,
-       })
-       if err != nil {
-               t.Fatalf("error creating seeder client: %s", err)
-       }
-       seeder.SetIPBlockList(nil)
+       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()
-       http.HandleFunc("/seeder", func(w http.ResponseWriter, req *http.Request) {
-               seeder.WriteStatus(w)
-       })
-       _, err = seeder.AddMagnet(fmt.Sprintf("magnet:?xt=urn:btih:%x", layout.Metainfo.Info.Hash))
-       if err != nil {
-               t.Fatal(err)
-       }
-       leecher, err := torrent.NewClient(&torrent.Config{
-               DisableTrackers: true,
-               NoDHT:           true,
-               ListenAddr:      ":0",
-               DisableTCP:      true,
-
-               NoDefaultBlocklist: true,
-
-               TorrentDataOpener: func(info *metainfo.Info) data.Data {
-                       ret, _ := mmap.TorrentData(info, filepath.Join(layout.BaseDir, "download"))
-                       return ret
-               },
-
-               // This can be used to check if clients can connect to other clients
-               // with the same ID.
-
-               // PeerID: seeder.PeerID(),
-       })
-       leecher.SetIPBlockList(nil)
-       http.HandleFunc("/leecher", func(w http.ResponseWriter, req *http.Request) {
-               leecher.WriteStatus(w)
-       })
+       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, _ := leecher.AddTorrent(layout.Metainfo)
-       leecherTorrent.AddPeers([]torrent.Peer{func() torrent.Peer {
-               _, port, err := net.SplitHostPort(seeder.ListenAddr().String())
-               if err != nil {
-                       panic(err)
-               }
-               portInt64, err := strconv.ParseInt(port, 0, 0)
-               if err != nil {
-                       panic(err)
-               }
-               return torrent.Peer{
-                       IP: func() net.IP {
-                               ret, _ := net.ResolveIPAddr("ip", "localhost")
-                               return ret.IP
-                       }(),
-                       Port: int(portInt64),
-               }
-       }()})
+       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(context.Background(), "greeting")
        var attr fuse.Attr
-       node.Attr(netContext.Background(), &attr)
+       node.Attr(context.Background(), &attr)
        size := attr.Size
-       resp := &fuse.ReadResponse{
-               Data: make([]byte, size),
-       }
-       node.(fusefs.HandleReader).Read(context.Background(), &fuse.ReadRequest{
-               Size: int(size),
-       }, resp)
-       content := resp.Data
-       if string(content) != testutil.GreetingFileContents {
-               t.Fatalf("%q != %q", string(content), testutil.GreetingFileContents)
+       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(context.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))
        }
 }