]> Sergey Matveev's repositories - btrtrc.git/blob - client_test.go
13e7ac8e5972478bf88bfcd27afc9a5f0e3acbf1
[btrtrc.git] / client_test.go
1 package torrent
2
3 import (
4         "encoding/binary"
5         "fmt"
6         "io"
7         "io/ioutil"
8         "log"
9         "net"
10         "os"
11         "path/filepath"
12         "testing"
13         "time"
14
15         _ "github.com/anacrolix/envpprof"
16         "github.com/anacrolix/utp"
17         "github.com/bradfitz/iter"
18         "github.com/stretchr/testify/assert"
19         "github.com/stretchr/testify/require"
20         "gopkg.in/check.v1"
21
22         "github.com/anacrolix/torrent/bencode"
23         "github.com/anacrolix/torrent/data"
24         "github.com/anacrolix/torrent/data/blob"
25         "github.com/anacrolix/torrent/internal/testutil"
26         "github.com/anacrolix/torrent/metainfo"
27         "github.com/anacrolix/torrent/util"
28 )
29
30 func init() {
31         log.SetFlags(log.LstdFlags | log.Lshortfile)
32 }
33
34 var TestingConfig = Config{
35         ListenAddr:           "localhost:0",
36         NoDHT:                true,
37         DisableTrackers:      true,
38         NoDefaultBlocklist:   true,
39         DisableMetainfoCache: true,
40         DataDir:              filepath.Join(os.TempDir(), "anacrolix"),
41 }
42
43 func TestClientDefault(t *testing.T) {
44         cl, err := NewClient(&TestingConfig)
45         if err != nil {
46                 t.Fatal(err)
47         }
48         cl.Close()
49 }
50
51 func TestAddDropTorrent(t *testing.T) {
52         cl, err := NewClient(&TestingConfig)
53         if err != nil {
54                 t.Fatal(err)
55         }
56         defer cl.Close()
57         dir, mi := testutil.GreetingTestTorrent()
58         defer os.RemoveAll(dir)
59         tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
60         if err != nil {
61                 t.Fatal(err)
62         }
63         if !new {
64                 t.FailNow()
65         }
66         tt.Drop()
67 }
68
69 func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
70         t.SkipNow()
71 }
72
73 func TestAddTorrentNoUsableURLs(t *testing.T) {
74         t.SkipNow()
75 }
76
77 func TestAddPeersToUnknownTorrent(t *testing.T) {
78         t.SkipNow()
79 }
80
81 func TestPieceHashSize(t *testing.T) {
82         if pieceHash.Size() != 20 {
83                 t.FailNow()
84         }
85 }
86
87 func TestTorrentInitialState(t *testing.T) {
88         dir, mi := testutil.GreetingTestTorrent()
89         defer os.RemoveAll(dir)
90         tor, err := newTorrent(func() (ih InfoHash) {
91                 util.CopyExact(ih[:], mi.Info.Hash)
92                 return
93         }())
94         if err != nil {
95                 t.Fatal(err)
96         }
97         tor.chunkSize = 2
98         err = tor.setMetadata(&mi.Info.Info, mi.Info.Bytes, nil)
99         if err != nil {
100                 t.Fatal(err)
101         }
102         if len(tor.Pieces) != 3 {
103                 t.Fatal("wrong number of pieces")
104         }
105         p := tor.Pieces[0]
106         tor.pendAllChunkSpecs(0)
107         assert.EqualValues(t, 3, p.numPendingChunks())
108         assert.EqualValues(t, chunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
109 }
110
111 func TestUnmarshalPEXMsg(t *testing.T) {
112         var m peerExchangeMessage
113         if err := bencode.Unmarshal([]byte("d5:added12:\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0ce"), &m); err != nil {
114                 t.Fatal(err)
115         }
116         if len(m.Added) != 2 {
117                 t.FailNow()
118         }
119         if m.Added[0].Port != 0x506 {
120                 t.FailNow()
121         }
122 }
123
124 func TestReducedDialTimeout(t *testing.T) {
125         for _, _case := range []struct {
126                 Max             time.Duration
127                 HalfOpenLimit   int
128                 PendingPeers    int
129                 ExpectedReduced time.Duration
130         }{
131                 {nominalDialTimeout, 40, 0, nominalDialTimeout},
132                 {nominalDialTimeout, 40, 1, nominalDialTimeout},
133                 {nominalDialTimeout, 40, 39, nominalDialTimeout},
134                 {nominalDialTimeout, 40, 40, nominalDialTimeout / 2},
135                 {nominalDialTimeout, 40, 80, nominalDialTimeout / 3},
136                 {nominalDialTimeout, 40, 4000, nominalDialTimeout / 101},
137         } {
138                 reduced := reducedDialTimeout(_case.Max, _case.HalfOpenLimit, _case.PendingPeers)
139                 expected := _case.ExpectedReduced
140                 if expected < minDialTimeout {
141                         expected = minDialTimeout
142                 }
143                 if reduced != expected {
144                         t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced)
145                 }
146         }
147 }
148
149 func TestUTPRawConn(t *testing.T) {
150         l, err := utp.NewSocket("")
151         if err != nil {
152                 t.Fatal(err)
153         }
154         defer l.Close()
155         go func() {
156                 for {
157                         _, err := l.Accept()
158                         if err != nil {
159                                 break
160                         }
161                 }
162         }()
163         // Connect a UTP peer to see if the RawConn will still work.
164         utpPeer, err := func() *utp.Socket {
165                 s, _ := utp.NewSocket("")
166                 return s
167         }().Dial(fmt.Sprintf("localhost:%d", util.AddrPort(l.Addr())))
168         if err != nil {
169                 t.Fatalf("error dialing utp listener: %s", err)
170         }
171         defer utpPeer.Close()
172         peer, err := net.ListenPacket("udp", ":0")
173         if err != nil {
174                 t.Fatal(err)
175         }
176         defer peer.Close()
177
178         msgsReceived := 0
179         // How many messages to send. I've set this to double the channel buffer
180         // size in the raw packetConn.
181         const N = 200
182         readerStopped := make(chan struct{})
183         // The reader goroutine.
184         go func() {
185                 defer close(readerStopped)
186                 b := make([]byte, 500)
187                 for i := 0; i < N; i++ {
188                         n, _, err := l.PacketConn().ReadFrom(b)
189                         if err != nil {
190                                 t.Fatalf("error reading from raw conn: %s", err)
191                         }
192                         msgsReceived++
193                         var d int
194                         fmt.Sscan(string(b[:n]), &d)
195                         if d != i {
196                                 log.Printf("got wrong number: expected %d, got %d", i, d)
197                         }
198                 }
199         }()
200         udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", util.AddrPort(l.Addr())))
201         if err != nil {
202                 t.Fatal(err)
203         }
204         for i := 0; i < N; i++ {
205                 _, err := peer.WriteTo([]byte(fmt.Sprintf("%d", i)), udpAddr)
206                 if err != nil {
207                         t.Fatal(err)
208                 }
209                 time.Sleep(time.Microsecond)
210         }
211         select {
212         case <-readerStopped:
213         case <-time.After(time.Second):
214                 t.Fatal("reader timed out")
215         }
216         if msgsReceived != N {
217                 t.Fatalf("messages received: %d", msgsReceived)
218         }
219 }
220
221 func TestTwoClientsArbitraryPorts(t *testing.T) {
222         for i := 0; i < 2; i++ {
223                 cl, err := NewClient(&TestingConfig)
224                 if err != nil {
225                         t.Fatal(err)
226                 }
227                 defer cl.Close()
228         }
229 }
230
231 func TestAddDropManyTorrents(t *testing.T) {
232         cl, _ := NewClient(&TestingConfig)
233         defer cl.Close()
234         for i := range iter.N(1000) {
235                 var spec TorrentSpec
236                 binary.PutVarint(spec.InfoHash[:], int64(i))
237                 tt, new, err := cl.AddTorrentSpec(&spec)
238                 if err != nil {
239                         t.Error(err)
240                 }
241                 if !new {
242                         t.FailNow()
243                 }
244                 defer tt.Drop()
245         }
246 }
247
248 func TestClientTransfer(t *testing.T) {
249         greetingTempDir, mi := testutil.GreetingTestTorrent()
250         defer os.RemoveAll(greetingTempDir)
251         cfg := TestingConfig
252         cfg.Seed = true
253         cfg.DataDir = greetingTempDir
254         seeder, err := NewClient(&cfg)
255         if err != nil {
256                 t.Fatal(err)
257         }
258         defer seeder.Close()
259         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
260         leecherDataDir, err := ioutil.TempDir("", "")
261         if err != nil {
262                 t.Fatal(err)
263         }
264         defer os.RemoveAll(leecherDataDir)
265         // cfg.TorrentDataOpener = func(info *metainfo.Info) (data.Data, error) {
266         //      return blob.TorrentData(info, leecherDataDir), nil
267         // }
268         cfg.TorrentDataOpener = blob.NewStore(leecherDataDir).OpenTorrent
269         leecher, _ := NewClient(&cfg)
270         defer leecher.Close()
271         leecherGreeting, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
272                 ret = TorrentSpecFromMetaInfo(mi)
273                 ret.ChunkSize = 2
274                 return
275         }())
276         leecherGreeting.AddPeers([]Peer{
277                 Peer{
278                         IP:   util.AddrIP(seeder.ListenAddr()),
279                         Port: util.AddrPort(seeder.ListenAddr()),
280                 },
281         })
282         r := leecherGreeting.NewReader()
283         defer r.Close()
284         _greeting, err := ioutil.ReadAll(r)
285         if err != nil {
286                 t.Fatalf("%q %s", string(_greeting), err)
287         }
288         greeting := string(_greeting)
289         if greeting != testutil.GreetingFileContents {
290                 t.Fatal(":(")
291         }
292 }
293
294 func TestReadaheadPieces(t *testing.T) {
295         for _, case_ := range []struct {
296                 readaheadBytes, pieceLength int64
297                 readaheadPieces             int
298         }{
299                 {5 * 1024 * 1024, 256 * 1024, 19},
300                 {5 * 1024 * 1024, 5 * 1024 * 1024, 1},
301                 {5*1024*1024 - 1, 5 * 1024 * 1024, 1},
302                 {5 * 1024 * 1024, 5*1024*1024 - 1, 2},
303                 {0, 5 * 1024 * 1024, 0},
304                 {5 * 1024 * 1024, 1048576, 4},
305         } {
306                 pieces := readaheadPieces(case_.readaheadBytes, case_.pieceLength)
307                 assert.Equal(t, case_.readaheadPieces, pieces, "%v", case_)
308         }
309 }
310
311 func (suite) TestMergingTrackersByAddingSpecs(c *check.C) {
312         cl, _ := NewClient(&TestingConfig)
313         defer cl.Close()
314         spec := TorrentSpec{}
315         T, new, _ := cl.AddTorrentSpec(&spec)
316         if !new {
317                 c.FailNow()
318         }
319         spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
320         _, new, _ = cl.AddTorrentSpec(&spec)
321         if new {
322                 c.FailNow()
323         }
324         c.Assert(T.Trackers[0][0].URL(), check.Equals, "http://a")
325         c.Assert(T.Trackers[1][0].URL(), check.Equals, "udp://b")
326 }
327
328 type badData struct {
329 }
330
331 func (me badData) WriteAt(b []byte, off int64) (int, error) {
332         return 0, nil
333 }
334
335 func (me badData) WriteSectionTo(w io.Writer, off, n int64) (int64, error) {
336         return 0, nil
337 }
338
339 func (me badData) PieceComplete(piece int) bool {
340         return true
341 }
342
343 func (me badData) PieceCompleted(piece int) error {
344         return nil
345 }
346
347 func (me badData) ReadAt(b []byte, off int64) (n int, err error) {
348         if off >= 5 {
349                 err = io.EOF
350                 return
351         }
352         n = copy(b, []byte("hello")[off:])
353         return
354 }
355
356 var _ StatefulData = badData{}
357
358 // We read from a piece which is marked completed, but is missing data.
359 func TestCompletedPieceWrongSize(t *testing.T) {
360         cfg := TestingConfig
361         cfg.TorrentDataOpener = func(*metainfo.Info) data.Data {
362                 return badData{}
363         }
364         cl, _ := NewClient(&cfg)
365         defer cl.Close()
366         tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
367                 Info: &metainfo.InfoEx{
368                         Info: metainfo.Info{
369                                 PieceLength: 15,
370                                 Pieces:      make([]byte, 20),
371                                 Files: []metainfo.FileInfo{
372                                         metainfo.FileInfo{Path: []string{"greeting"}, Length: 13},
373                                 },
374                         },
375                 },
376         })
377         if err != nil {
378                 t.Fatal(err)
379         }
380         if !new {
381                 t.Fatal("expected new")
382         }
383         r := tt.NewReader()
384         defer r.Close()
385         b := make([]byte, 20)
386         n, err := io.ReadFull(r, b)
387         if n != 5 || err != io.ErrUnexpectedEOF {
388                 t.Fatal(n, err)
389         }
390         defer tt.Drop()
391 }
392
393 func BenchmarkAddLargeTorrent(b *testing.B) {
394         cfg := TestingConfig
395         cfg.DisableTCP = true
396         cfg.DisableUTP = true
397         cfg.ListenAddr = "redonk"
398         cl, _ := NewClient(&cfg)
399         defer cl.Close()
400         for range iter.N(b.N) {
401                 t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent")
402                 if err != nil {
403                         b.Fatal(err)
404                 }
405                 t.Drop()
406         }
407 }
408
409 func TestResponsive(t *testing.T) {
410         seederDataDir, mi := testutil.GreetingTestTorrent()
411         defer os.RemoveAll(seederDataDir)
412         cfg := TestingConfig
413         cfg.Seed = true
414         cfg.DataDir = seederDataDir
415         seeder, err := NewClient(&cfg)
416         require.Nil(t, err)
417         defer seeder.Close()
418         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
419         leecherDataDir, err := ioutil.TempDir("", "")
420         require.Nil(t, err)
421         defer os.RemoveAll(leecherDataDir)
422         cfg = TestingConfig
423         cfg.DataDir = leecherDataDir
424         leecher, err := NewClient(&cfg)
425         require.Nil(t, err)
426         defer leecher.Close()
427         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
428                 ret = TorrentSpecFromMetaInfo(mi)
429                 ret.ChunkSize = 2
430                 return
431         }())
432         leecherTorrent.AddPeers([]Peer{
433                 Peer{
434                         IP:   util.AddrIP(seeder.ListenAddr()),
435                         Port: util.AddrPort(seeder.ListenAddr()),
436                 },
437         })
438         reader := leecherTorrent.NewReader()
439         reader.SetReadahead(0)
440         reader.SetResponsive()
441         b := make([]byte, 2)
442         _, err = reader.ReadAt(b, 3)
443         assert.Nil(t, err)
444         assert.EqualValues(t, "lo", string(b))
445         n, err := reader.ReadAt(b, 11)
446         assert.Nil(t, err)
447         assert.EqualValues(t, 2, n)
448         assert.EqualValues(t, "d\n", string(b))
449 }