unixfs_test.go 23.1 KB
Newer Older
1 2 3 4 5
package coreapi_test

import (
	"bytes"
	"context"
Łukasz Magiera's avatar
Łukasz Magiera committed
6
	"encoding/base64"
7
	"fmt"
8
	"io"
9
	"io/ioutil"
10
	"math"
11
	"os"
12
	"strconv"
13
	"strings"
14
	"sync"
15 16
	"testing"

17 18
	"github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/core/coreapi"
19
	coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
20 21
	"github.com/ipfs/go-ipfs/core/coreapi/interface/options"
	"github.com/ipfs/go-ipfs/core/coreunix"
22
	mock "github.com/ipfs/go-ipfs/core/mock"
23 24
	"github.com/ipfs/go-ipfs/keystore"
	"github.com/ipfs/go-ipfs/repo"
25

Steven Allen's avatar
Steven Allen committed
26
	ci "gx/ipfs/QmNiJiXwWE3kRhZrC5ej3kSjWHm337pYfhjLGSCDNKJP2s/go-libp2p-crypto"
Steven Allen's avatar
Steven Allen committed
27
	mocknet "gx/ipfs/QmRBaUEQEeFWywfrZJ64QgsmvcqgLSK3VbvGMR2NM2Edpf/go-libp2p/p2p/net/mock"
Steven Allen's avatar
Steven Allen committed
28
	cbor "gx/ipfs/QmRoARq3nkUb13HSKZGepCZSWe5GrVPwx7xURJGZ7KWv9V/go-ipld-cbor"
29
	files "gx/ipfs/QmXWZCd8jfaHmt4UDSnjKmGcrQMw95bDGWqEeVLVJjoANX/go-ipfs-files"
Steven Allen's avatar
Steven Allen committed
30 31 32
	peer "gx/ipfs/QmY5Grm8pJdiSSVsYxx4uNRgweY72EmYwuSDbRnbFok3iY/go-libp2p-peer"
	config "gx/ipfs/QmYyzmMnhNTtoXx5ttgUaRdHHckYnQWjPL98hgLAR2QLDD/go-ipfs-config"
	pstore "gx/ipfs/QmZ9zH2FnLcxv1xyzFeUpDUeo55xEhZQHgveZijcxr7TLj/go-libp2p-peerstore"
33
	unixfs "gx/ipfs/Qmbvw7kpSM2p6rbQ57WGRhhqNfCiNGW6EKH4xgHLw4bsnB/go-unixfs"
Steven Allen's avatar
Steven Allen committed
34
	mdag "gx/ipfs/QmdV35UHnL1FM52baPkeUo6u7Fxm2CRUkPTLRPxeF8a4Ap/go-merkledag"
Steven Allen's avatar
Steven Allen committed
35
	mh "gx/ipfs/QmerPMzPk1mJVowm8KgmoknWa4yCYvvugMPsgWmDNUvDLW/go-multihash"
36 37
	datastore "gx/ipfs/Qmf4xQhNomPNhrtZc67qSnfJSjxjXs9LWvknJtSXwimPrM/go-datastore"
	syncds "gx/ipfs/Qmf4xQhNomPNhrtZc67qSnfJSjxjXs9LWvknJtSXwimPrM/go-datastore/sync"
38 39
)

40 41
const testPeerID = "QmTFauExutTsy4XP6JbMFcw2Wa9645HJt2bTqL6qYDCKfe"

Lars Gierth's avatar
Lars Gierth committed
42
// `echo -n 'hello, world!' | ipfs add`
43
var hello = "/ipfs/QmQy2Dw4Wk7rdJKjThjYXzfFJNaRKRHhHP5gHHXroJMYxk"
Lars Gierth's avatar
Lars Gierth committed
44 45
var helloStr = "hello, world!"

46
// `echo -n | ipfs add`
47
var emptyFile = "/ipfs/QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH"
48

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
func makeAPISwarm(ctx context.Context, fullIdentity bool, n int) ([]*core.IpfsNode, []coreiface.CoreAPI, error) {
	mn := mocknet.New(ctx)

	nodes := make([]*core.IpfsNode, n)
	apis := make([]coreiface.CoreAPI, n)

	for i := 0; i < n; i++ {
		var ident config.Identity
		if fullIdentity {
			sk, pk, err := ci.GenerateKeyPair(ci.RSA, 512)
			if err != nil {
				return nil, nil, err
			}

			id, err := peer.IDFromPublicKey(pk)
			if err != nil {
				return nil, nil, err
			}

			kbytes, err := sk.Bytes()
			if err != nil {
				return nil, nil, err
			}

			ident = config.Identity{
				PeerID:  id.Pretty(),
				PrivKey: base64.StdEncoding.EncodeToString(kbytes),
			}
		} else {
			ident = config.Identity{
				PeerID: testPeerID,
			}
Łukasz Magiera's avatar
Łukasz Magiera committed
81 82
		}

83 84 85 86 87 88 89 90
		c := config.Config{}
		c.Addresses.Swarm = []string{fmt.Sprintf("/ip4/127.0.%d.1/tcp/4001", i)}
		c.Identity = ident

		r := &repo.Mock{
			C: c,
			D: syncds.MutexWrap(datastore.NewMapDatastore()),
			K: keystore.NewMemKeystore(),
Łukasz Magiera's avatar
Łukasz Magiera committed
91 92
		}

93 94 95 96
		node, err := core.NewNode(ctx, &core.BuildCfg{
			Repo:   r,
			Host:   mock.MockHostOption(mn),
			Online: fullIdentity,
Łukasz Magiera's avatar
Łukasz Magiera committed
97 98 99
			ExtraOpts: map[string]bool{
				"pubsub": true,
			},
100
		})
Łukasz Magiera's avatar
Łukasz Magiera committed
101 102 103
		if err != nil {
			return nil, nil, err
		}
104 105 106
		nodes[i] = node
		apis[i] = coreapi.NewCoreAPI(node)
	}
Łukasz Magiera's avatar
Łukasz Magiera committed
107

108 109 110
	err := mn.LinkAll()
	if err != nil {
		return nil, nil, err
Łukasz Magiera's avatar
Łukasz Magiera committed
111 112
	}

113 114 115
	bsinf := core.BootstrapConfigWithPeers(
		[]pstore.PeerInfo{
			nodes[0].Peerstore.PeerInfo(nodes[0].Identity),
116
		},
117 118 119 120 121 122
	)

	for _, n := range nodes[1:] {
		if err := n.Bootstrap(bsinf); err != nil {
			return nil, nil, err
		}
123
	}
124 125 126 127 128 129

	return nodes, apis, nil
}

func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.CoreAPI, error) {
	nd, api, err := makeAPISwarm(ctx, false, 1)
130 131 132 133
	if err != nil {
		return nil, nil, err
	}

134
	return nd[0], api[0], nil
Łukasz Magiera's avatar
Łukasz Magiera committed
135 136
}

137 138
func strFile(data string) func() files.Node {
	return func() files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
139
		return files.NewBytesFile([]byte(data))
140 141 142
	}
}

143 144
func twoLevelDir() func() files.Node {
	return func() files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
145 146 147
		return files.NewMapDirectory(map[string]files.Node{
			"abc": files.NewMapDirectory(map[string]files.Node{
				"def": files.NewBytesFile([]byte("world")),
148
			}),
Łukasz Magiera's avatar
Łukasz Magiera committed
149

Łukasz Magiera's avatar
Łukasz Magiera committed
150 151
			"bar": files.NewBytesFile([]byte("hello2")),
			"foo": files.NewBytesFile([]byte("hello1")),
Łukasz Magiera's avatar
Łukasz Magiera committed
152 153 154 155
		})
	}
}

156
func flatDir() files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
157 158 159
	return files.NewMapDirectory(map[string]files.Node{
		"bar": files.NewBytesFile([]byte("hello2")),
		"foo": files.NewBytesFile([]byte("hello1")),
160 161 162
	})
}

163 164
func wrapped(name string) func(f files.Node) files.Node {
	return func(f files.Node) files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
165
		return files.NewMapDirectory(map[string]files.Node{
166
			name: f,
Łukasz Magiera's avatar
Łukasz Magiera committed
167 168
		})
	}
169 170
}

Lars Gierth's avatar
Lars Gierth committed
171 172 173 174 175 176 177
func TestAdd(t *testing.T) {
	ctx := context.Background()
	_, api, err := makeAPI(ctx)
	if err != nil {
		t.Error(err)
	}

178
	cases := []struct {
179
		name   string
180 181
		data   func() files.Node
		expect func(files.Node) files.Node
Łukasz Magiera's avatar
Łukasz Magiera committed
182 183 184 185

		path string
		err  string

Łukasz Magiera's avatar
Łukasz Magiera committed
186
		wrap string
Łukasz Magiera's avatar
Łukasz Magiera committed
187

188 189
		events []coreiface.AddEvent

Łukasz Magiera's avatar
Łukasz Magiera committed
190
		opts []options.UnixfsAddOption
191
	}{
192
		// Simple cases
193 194
		{
			name: "simpleAdd",
195
			data: strFile(helloStr),
196 197 198 199 200
			path: hello,
			opts: []options.UnixfsAddOption{},
		},
		{
			name: "addEmpty",
201
			data: strFile(""),
202
			path: emptyFile,
203
		},
204
		// CIDv1 version / rawLeaves
205 206
		{
			name: "addCidV1",
207
			data: strFile(helloStr),
208 209 210
			path: "/ipfs/zb2rhdhmJjJZs9qkhQCpCQ7VREFkqWw3h1r8utjVvQugwHPFd",
			opts: []options.UnixfsAddOption{options.Unixfs.CidVersion(1)},
		},
211 212
		{
			name: "addCidV1NoLeaves",
213
			data: strFile(helloStr),
214 215 216 217
			path: "/ipfs/zdj7WY4GbN8NDbTW1dfCShAQNVovams2xhq9hVCx5vXcjvT8g",
			opts: []options.UnixfsAddOption{options.Unixfs.CidVersion(1), options.Unixfs.RawLeaves(false)},
		},
		// Non sha256 hash vs CID
218 219
		{
			name: "addCidSha3",
220
			data: strFile(helloStr),
221 222 223 224 225
			path: "/ipfs/zb2wwnYtXBxpndNABjtYxWAPt3cwWNRnc11iT63fvkYV78iRb",
			opts: []options.UnixfsAddOption{options.Unixfs.Hash(mh.SHA3_256)},
		},
		{
			name: "addCidSha3Cid0",
226
			data: strFile(helloStr),
227 228 229
			err:  "CIDv0 only supports sha2-256",
			opts: []options.UnixfsAddOption{options.Unixfs.CidVersion(0), options.Unixfs.Hash(mh.SHA3_256)},
		},
230 231 232
		// Inline
		{
			name: "addInline",
233
			data: strFile(helloStr),
234
			path: "/ipfs/zaYomJdLndMku8P9LHngHB5w2CQ7NenLbv",
235 236 237 238
			opts: []options.UnixfsAddOption{options.Unixfs.Inline(true)},
		},
		{
			name: "addInlineLimit",
239
			data: strFile(helloStr),
240 241 242 243 244
			path: "/ipfs/zaYomJdLndMku8P9LHngHB5w2CQ7NenLbv",
			opts: []options.UnixfsAddOption{options.Unixfs.InlineLimit(32), options.Unixfs.Inline(true)},
		},
		{
			name: "addInlineZero",
245
			data: strFile(""),
246 247
			path: "/ipfs/z2yYDV",
			opts: []options.UnixfsAddOption{options.Unixfs.InlineLimit(0), options.Unixfs.Inline(true), options.Unixfs.RawLeaves(true)},
248 249 250
		},
		{ //TODO: after coreapi add is used in `ipfs add`, consider making this default for inline
			name: "addInlineRaw",
251
			data: strFile(helloStr),
252
			path: "/ipfs/zj7Gr8AcBreqGEfrnR5kPFe",
253
			opts: []options.UnixfsAddOption{options.Unixfs.InlineLimit(32), options.Unixfs.Inline(true), options.Unixfs.RawLeaves(true)},
254
		},
255 256 257
		// Chunker / Layout
		{
			name: "addChunks",
258
			data: strFile(strings.Repeat("aoeuidhtns", 200)),
259 260 261 262 263
			path: "/ipfs/QmRo11d4QJrST47aaiGVJYwPhoNA4ihRpJ5WaxBWjWDwbX",
			opts: []options.UnixfsAddOption{options.Unixfs.Chunker("size-4")},
		},
		{
			name: "addChunksTrickle",
264
			data: strFile(strings.Repeat("aoeuidhtns", 200)),
265
			path: "/ipfs/QmNNhDGttafX3M1wKWixGre6PrLFGjnoPEDXjBYpTv93HP",
266
			opts: []options.UnixfsAddOption{options.Unixfs.Chunker("size-4"), options.Unixfs.Layout(options.TrickleLayout)},
267
		},
268 269 270
		// Local
		{
			name: "addLocal", // better cases in sharness
271
			data: strFile(helloStr),
272 273 274
			path: hello,
			opts: []options.UnixfsAddOption{options.Unixfs.Local(true)},
		},
Łukasz Magiera's avatar
Łukasz Magiera committed
275 276 277 278 279 280
		{
			name: "hashOnly", // test (non)fetchability
			data: strFile(helloStr),
			path: hello,
			opts: []options.UnixfsAddOption{options.Unixfs.HashOnly(true)},
		},
281 282
		// multi file
		{
Łukasz Magiera's avatar
Łukasz Magiera committed
283 284 285 286
			name: "simpleDir",
			data: flatDir,
			wrap: "t",
			path: "/ipfs/QmRKGpFfR32FVXdvJiHfo4WJ5TDYBsM1P9raAp1p6APWSp",
287 288
		},
		{
Łukasz Magiera's avatar
Łukasz Magiera committed
289 290 291 292
			name: "twoLevelDir",
			data: twoLevelDir(),
			wrap: "t",
			path: "/ipfs/QmVG2ZYCkV1S4TK8URA3a4RupBF17A8yAr4FqsRDXVJASr",
Łukasz Magiera's avatar
Łukasz Magiera committed
293 294 295 296 297
		},
		// wrapped
		{
			name: "addWrapped",
			path: "/ipfs/QmVE9rNpj5doj7XHzp5zMUxD7BJgXEqx4pe3xZ3JBReWHE",
298
			data: func() files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
299
				return files.NewBytesFile([]byte(helloStr))
300
			},
Łukasz Magiera's avatar
Łukasz Magiera committed
301
			wrap:   "foo",
Łukasz Magiera's avatar
Łukasz Magiera committed
302
			expect: wrapped("foo"),
303
			opts:   []options.UnixfsAddOption{options.Unixfs.Wrap(true)},
Łukasz Magiera's avatar
Łukasz Magiera committed
304
		},
305 306 307
		{
			name: "addNotWrappedDirFile",
			path: hello,
308
			data: func() files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
309
				return files.NewBytesFile([]byte(helloStr))
310 311 312
			},
			wrap: "foo",
		},
313 314 315
		{
			name: "stdinWrapped",
			path: "/ipfs/QmU3r81oZycjHS9oaSHw37ootMFuFUw1DvMLKXPsezdtqU",
316
			data: func() files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
317
				return files.NewBytesFile([]byte(helloStr))
318
			},
319
			expect: func(files.Node) files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
320 321
				return files.NewMapDirectory(map[string]files.Node{
					"QmQy2Dw4Wk7rdJKjThjYXzfFJNaRKRHhHP5gHHXroJMYxk": files.NewBytesFile([]byte(helloStr)),
322 323
				})
			},
324
			opts: []options.UnixfsAddOption{options.Unixfs.Wrap(true)},
325 326 327 328
		},
		{
			name: "stdinNamed",
			path: "/ipfs/QmQ6cGBmb3ZbdrQW1MRm1RJnYnaxCqfssz7CrTa9NEhQyS",
329
			data: func() files.Node {
330 331 332 333 334 335
				rf, err := files.NewReaderPathFile(os.Stdin.Name(), ioutil.NopCloser(strings.NewReader(helloStr)), nil)
				if err != nil {
					panic(err)
				}

				return rf
336
			},
337
			expect: func(files.Node) files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
338 339
				return files.NewMapDirectory(map[string]files.Node{
					"test": files.NewBytesFile([]byte(helloStr)),
340 341
				})
			},
342
			opts: []options.UnixfsAddOption{options.Unixfs.Wrap(true), options.Unixfs.StdinName("test")},
343
		},
Łukasz Magiera's avatar
Łukasz Magiera committed
344
		{
Łukasz Magiera's avatar
Łukasz Magiera committed
345 346 347
			name:   "twoLevelDirWrapped",
			data:   twoLevelDir(),
			wrap:   "t",
348
			expect: wrapped("t"),
Łukasz Magiera's avatar
Łukasz Magiera committed
349 350
			path:   "/ipfs/QmPwsL3T5sWhDmmAWZHAzyjKtMVDS9a11aHNRqb3xoVnmg",
			opts:   []options.UnixfsAddOption{options.Unixfs.Wrap(true)},
Łukasz Magiera's avatar
Łukasz Magiera committed
351 352
		},
		{
Łukasz Magiera's avatar
Łukasz Magiera committed
353 354 355
			name:   "twoLevelInlineHash",
			data:   twoLevelDir(),
			wrap:   "t",
356
			expect: wrapped("t"),
Łukasz Magiera's avatar
Łukasz Magiera committed
357 358
			path:   "/ipfs/zBunoruKoyCHKkALNSWxDvj4L7yuQnMgQ4hUa9j1Z64tVcDEcu6Zdetyu7eeFCxMPfxb7YJvHeFHoFoHMkBUQf6vfdhmi",
			opts:   []options.UnixfsAddOption{options.Unixfs.Wrap(true), options.Unixfs.Inline(true), options.Unixfs.RawLeaves(true), options.Unixfs.Hash(mh.SHA3)},
359
		},
360 361 362
		// hidden
		{
			name: "hiddenFiles",
363
			data: func() files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
364 365 366 367
				return files.NewMapDirectory(map[string]files.Node{
					".bar": files.NewBytesFile([]byte("hello2")),
					"bar":  files.NewBytesFile([]byte("hello2")),
					"foo":  files.NewBytesFile([]byte("hello1")),
368 369
				})
			},
Łukasz Magiera's avatar
Łukasz Magiera committed
370 371 372
			wrap: "t",
			path: "/ipfs/QmehGvpf2hY196MzDFmjL8Wy27S4jbgGDUAhBJyvXAwr3g",
			opts: []options.UnixfsAddOption{options.Unixfs.Hidden(true)},
373 374 375
		},
		{
			name: "hiddenFileAlwaysAdded",
376
			data: func() files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
377
				return files.NewBytesFile([]byte(helloStr))
378
			},
Łukasz Magiera's avatar
Łukasz Magiera committed
379 380
			wrap: ".foo",
			path: hello,
381 382 383
		},
		{
			name: "hiddenFilesNotAdded",
384
			data: func() files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
385 386 387 388
				return files.NewMapDirectory(map[string]files.Node{
					".bar": files.NewBytesFile([]byte("hello2")),
					"bar":  files.NewBytesFile([]byte("hello2")),
					"foo":  files.NewBytesFile([]byte("hello1")),
389 390
				})
			},
391
			expect: func(files.Node) files.Node {
392
				return flatDir()
393
			},
Łukasz Magiera's avatar
Łukasz Magiera committed
394 395 396
			wrap: "t",
			path: "/ipfs/QmRKGpFfR32FVXdvJiHfo4WJ5TDYBsM1P9raAp1p6APWSp",
			opts: []options.UnixfsAddOption{options.Unixfs.Hidden(false)},
397
		},
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
		// Events / Progress
		{
			name: "simpleAddEvent",
			data: strFile(helloStr),
			path: "/ipfs/zb2rhdhmJjJZs9qkhQCpCQ7VREFkqWw3h1r8utjVvQugwHPFd",
			events: []coreiface.AddEvent{
				{Name: "zb2rhdhmJjJZs9qkhQCpCQ7VREFkqWw3h1r8utjVvQugwHPFd", Hash: "zb2rhdhmJjJZs9qkhQCpCQ7VREFkqWw3h1r8utjVvQugwHPFd", Size: strconv.Itoa(len(helloStr))},
			},
			opts: []options.UnixfsAddOption{options.Unixfs.RawLeaves(true)},
		},
		{
			name: "silentAddEvent",
			data: twoLevelDir(),
			path: "/ipfs/QmVG2ZYCkV1S4TK8URA3a4RupBF17A8yAr4FqsRDXVJASr",
			events: []coreiface.AddEvent{
				{Name: "t/abc", Hash: "QmU7nuGs2djqK99UNsNgEPGh6GV4662p6WtsgccBNGTDxt", Size: "62"},
				{Name: "t", Hash: "QmVG2ZYCkV1S4TK8URA3a4RupBF17A8yAr4FqsRDXVJASr", Size: "229"},
			},
Łukasz Magiera's avatar
Łukasz Magiera committed
416 417
			wrap: "t",
			opts: []options.UnixfsAddOption{options.Unixfs.Silent(true)},
418 419 420 421 422 423 424 425 426 427 428 429
		},
		{
			name: "dirAddEvents",
			data: twoLevelDir(),
			path: "/ipfs/QmVG2ZYCkV1S4TK8URA3a4RupBF17A8yAr4FqsRDXVJASr",
			events: []coreiface.AddEvent{
				{Name: "t/abc/def", Hash: "QmNyJpQkU1cEkBwMDhDNFstr42q55mqG5GE5Mgwug4xyGk", Size: "13"},
				{Name: "t/bar", Hash: "QmS21GuXiRMvJKHos4ZkEmQDmRBqRaF5tQS2CQCu2ne9sY", Size: "14"},
				{Name: "t/foo", Hash: "QmfAjGiVpTN56TXi6SBQtstit5BEw3sijKj1Qkxn6EXKzJ", Size: "14"},
				{Name: "t/abc", Hash: "QmU7nuGs2djqK99UNsNgEPGh6GV4662p6WtsgccBNGTDxt", Size: "62"},
				{Name: "t", Hash: "QmVG2ZYCkV1S4TK8URA3a4RupBF17A8yAr4FqsRDXVJASr", Size: "229"},
			},
Łukasz Magiera's avatar
Łukasz Magiera committed
430
			wrap: "t",
431 432 433
		},
		{
			name: "progress1M",
434
			data: func() files.Node {
Łukasz Magiera's avatar
Łukasz Magiera committed
435
				return files.NewReaderFile(bytes.NewReader(bytes.Repeat([]byte{0}, 1000000)))
436 437 438 439 440 441 442 443 444
			},
			path: "/ipfs/QmXXNNbwe4zzpdMg62ZXvnX1oU7MwSrQ3vAEtuwFKCm1oD",
			events: []coreiface.AddEvent{
				{Name: "", Bytes: 262144},
				{Name: "", Bytes: 524288},
				{Name: "", Bytes: 786432},
				{Name: "", Bytes: 1000000},
				{Name: "QmXXNNbwe4zzpdMg62ZXvnX1oU7MwSrQ3vAEtuwFKCm1oD", Hash: "QmXXNNbwe4zzpdMg62ZXvnX1oU7MwSrQ3vAEtuwFKCm1oD", Size: "1000256"},
			},
445
			wrap: "",
Łukasz Magiera's avatar
Łukasz Magiera committed
446
			opts: []options.UnixfsAddOption{options.Unixfs.Progress(true)},
447
		},
448 449
	}

450 451
	for _, testCase := range cases {
		t.Run(testCase.name, func(t *testing.T) {
452 453 454 455
			ctx, cancel := context.WithCancel(ctx)
			defer cancel()

			// recursive logic
456 457

			data := testCase.data()
Łukasz Magiera's avatar
Łukasz Magiera committed
458
			if testCase.wrap != "" {
Łukasz Magiera's avatar
Łukasz Magiera committed
459 460
				data = files.NewMapDirectory(map[string]files.Node{
					testCase.wrap: data,
461 462 463
				})
			}

464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
			// handle events if relevant to test case

			opts := testCase.opts
			eventOut := make(chan interface{})
			var evtWg sync.WaitGroup
			if len(testCase.events) > 0 {
				opts = append(opts, options.Unixfs.Events(eventOut))
				evtWg.Add(1)

				go func() {
					defer evtWg.Done()
					expected := testCase.events

					for evt := range eventOut {
						event, ok := evt.(*coreiface.AddEvent)
						if !ok {
							t.Fatal("unexpected event type")
						}

						if len(expected) < 1 {
							t.Fatal("got more events than expected")
						}

						if expected[0].Size != event.Size {
							t.Errorf("Event.Size didn't match, %s != %s", expected[0].Size, event.Size)
						}

						if expected[0].Name != event.Name {
							t.Errorf("Event.Name didn't match, %s != %s", expected[0].Name, event.Name)
						}

						if expected[0].Hash != event.Hash {
							t.Errorf("Event.Hash didn't match, %s != %s", expected[0].Hash, event.Hash)
						}
						if expected[0].Bytes != event.Bytes {
							t.Errorf("Event.Bytes didn't match, %d != %d", expected[0].Bytes, event.Bytes)
						}

						expected = expected[1:]
					}

					if len(expected) > 0 {
						t.Fatalf("%d event(s) didn't arrive", len(expected))
					}
				}()
			}

			// Add!

			p, err := api.Unixfs().Add(ctx, data, opts...)
			close(eventOut)
			evtWg.Wait()
516 517 518 519 520 521 522 523 524 525
			if testCase.err != "" {
				if err == nil {
					t.Fatalf("expected an error: %s", testCase.err)
				}
				if err.Error() != testCase.err {
					t.Fatalf("expected an error: '%s' != '%s'", err.Error(), testCase.err)
				}
				return
			}
			if err != nil {
526
				t.Fatal(err)
527
			}
Lars Gierth's avatar
Lars Gierth committed
528

529
			if p.String() != testCase.path {
530
				t.Errorf("expected path %s, got: %s", testCase.path, p)
531
			}
532

533 534
			// compare file structure with Unixfs().Get

535 536 537 538 539 540
			var cmpFile func(origName string, orig files.Node, gotName string, got files.Node)
			cmpFile = func(origName string, orig files.Node, gotName string, got files.Node) {
				_, origDir := orig.(files.Directory)
				_, gotDir := got.(files.Directory)

				if origDir != gotDir {
541 542 543
					t.Fatal("file type mismatch")
				}

Łukasz Magiera's avatar
Łukasz Magiera committed
544
				if origName != gotName {
545
					t.Errorf("file name mismatch, orig='%s', got='%s'", origName, gotName)
Łukasz Magiera's avatar
Łukasz Magiera committed
546 547
				}

548
				if !gotDir {
549 550 551
					defer orig.Close()
					defer got.Close()

552
					do, err := ioutil.ReadAll(orig.(files.File))
553 554 555 556
					if err != nil {
						t.Fatal(err)
					}

557
					dg, err := ioutil.ReadAll(got.(files.File))
558 559 560 561 562 563 564 565 566 567 568
					if err != nil {
						t.Fatal(err)
					}

					if !bytes.Equal(do, dg) {
						t.Fatal("data not equal")
					}

					return
				}

569 570
				origIt := orig.(files.Directory).Entries()
				gotIt := got.(files.Directory).Entries()
571

572 573 574 575
				for {
					if origIt.Next() {
						if !gotIt.Next() {
							t.Fatal("gotIt out of entries before origIt")
576
						}
577 578 579 580 581
					} else {
						if gotIt.Next() {
							t.Fatal("origIt out of entries before gotIt")
						}
						break
582 583
					}

584 585 586 587 588 589 590
					cmpFile(origIt.Name(), origIt.Node(), gotIt.Name(), gotIt.Node())
				}
				if origIt.Err() != nil {
					t.Fatal(origIt.Err())
				}
				if gotIt.Err() != nil {
					t.Fatal(gotIt.Err())
591
				}
592
			}
593 594

			f, err := api.Unixfs().Get(ctx, p)
595
			if err != nil {
596
				t.Fatal(err)
597
			}
598

Łukasz Magiera's avatar
Łukasz Magiera committed
599
			orig := testCase.data()
600 601
			if testCase.expect != nil {
				orig = testCase.expect(orig)
Łukasz Magiera's avatar
Łukasz Magiera committed
602 603
			}

Łukasz Magiera's avatar
Łukasz Magiera committed
604
			cmpFile("", orig, "", f)
605
		})
606 607 608
	}
}

609 610 611 612 613 614 615
func TestAddPinned(t *testing.T) {
	ctx := context.Background()
	_, api, err := makeAPI(ctx)
	if err != nil {
		t.Error(err)
	}

616
	_, err = api.Unixfs().Add(ctx, strFile(helloStr)(), options.Unixfs.Pin(true))
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
	if err != nil {
		t.Error(err)
	}

	pins, err := api.Pin().Ls(ctx)
	if len(pins) != 1 {
		t.Fatalf("expected 1 pin, got %d", len(pins))
	}

	if pins[0].Path().String() != "/ipld/QmQy2Dw4Wk7rdJKjThjYXzfFJNaRKRHhHP5gHHXroJMYxk" {
		t.Fatalf("got unexpected pin: %s", pins[0].Path().String())
	}
}

func TestAddHashOnly(t *testing.T) {
	ctx := context.Background()
	_, api, err := makeAPI(ctx)
	if err != nil {
		t.Error(err)
	}

638
	p, err := api.Unixfs().Add(ctx, strFile(helloStr)(), options.Unixfs.HashOnly(true))
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
	if err != nil {
		t.Error(err)
	}

	if p.String() != hello {
		t.Errorf("unxepected path: %s", p.String())
	}

	_, err = api.Block().Get(ctx, p)
	if err == nil {
		t.Fatal("expected an error")
	}
	if err.Error() != "blockservice: key not found" {
		t.Errorf("unxepected error: %s", err.Error())
	}
}

656
func TestGetEmptyFile(t *testing.T) {
657 658 659 660 661 662 663 664 665 666 667
	ctx := context.Background()
	node, api, err := makeAPI(ctx)
	if err != nil {
		t.Fatal(err)
	}

	_, err = coreunix.Add(node, strings.NewReader(""))
	if err != nil {
		t.Fatal(err)
	}

668
	emptyFilePath, err := coreiface.ParsePath(emptyFile)
669 670 671 672
	if err != nil {
		t.Fatal(err)
	}

673
	r, err := api.Unixfs().Get(ctx, emptyFilePath)
674 675 676 677 678
	if err != nil {
		t.Fatal(err)
	}

	buf := make([]byte, 1) // non-zero so that Read() actually tries to read
679
	n, err := io.ReadFull(r.(files.File), buf)
680 681 682 683 684 685 686 687
	if err != nil && err != io.EOF {
		t.Error(err)
	}
	if !bytes.HasPrefix(buf, []byte{0x00}) {
		t.Fatalf("expected empty data, got [%s] [read=%d]", buf, n)
	}
}

688
func TestGetDir(t *testing.T) {
689 690 691 692 693
	ctx := context.Background()
	node, api, err := makeAPI(ctx)
	if err != nil {
		t.Error(err)
	}
694 695
	edir := unixfs.EmptyDirNode()
	err = node.DAG.Add(ctx, edir)
696 697 698
	if err != nil {
		t.Error(err)
	}
699
	p := coreiface.IpfsPath(edir.Cid())
700

701
	emptyDir, err := api.Object().New(ctx, options.Object.Type("unixfs-dir"))
702 703
	if err != nil {
		t.Error(err)
704
	}
705

706
	if p.String() != coreiface.IpfsPath(emptyDir.Cid()).String() {
707 708 709
		t.Fatalf("expected path %s, got: %s", emptyDir.Cid(), p.String())
	}

710 711 712 713 714
	r, err := api.Unixfs().Get(ctx, coreiface.IpfsPath(emptyDir.Cid()))
	if err != nil {
		t.Error(err)
	}

715 716
	if _, ok := r.(files.Directory); !ok {
		t.Fatalf("expected a directory")
717 718 719
	}
}

720
func TestGetNonUnixfs(t *testing.T) {
721 722 723 724 725 726
	ctx := context.Background()
	node, api, err := makeAPI(ctx)
	if err != nil {
		t.Error(err)
	}

727 728
	nd := new(mdag.ProtoNode)
	err = node.DAG.Add(ctx, nd)
729 730 731 732
	if err != nil {
		t.Error(err)
	}

733
	_, err = api.Unixfs().Get(ctx, coreiface.IpfsPath(nd.Cid()))
734 735 736 737 738 739 740 741 742 743 744 745 746
	if !strings.Contains(err.Error(), "proto: required field") {
		t.Fatalf("expected protobuf error, got: %s", err)
	}
}

func TestLs(t *testing.T) {
	ctx := context.Background()
	node, api, err := makeAPI(ctx)
	if err != nil {
		t.Error(err)
	}

	r := strings.NewReader("content-of-file")
747
	k, _, err := coreunix.AddWrapped(node, r, "name-of-file")
748 749 750
	if err != nil {
		t.Error(err)
	}
751
	parts := strings.Split(k, "/")
752
	if len(parts) != 2 {
Jakub Sztandera's avatar
Jakub Sztandera committed
753
		t.Errorf("unexpected path: %s", k)
754
	}
755
	p, err := coreiface.ParsePath("/ipfs/" + parts[0])
756 757 758
	if err != nil {
		t.Error(err)
	}
759

Łukasz Magiera's avatar
Łukasz Magiera committed
760
	links, err := api.Unixfs().Ls(ctx, p)
761 762 763 764 765 766 767 768 769 770 771 772 773 774
	if err != nil {
		t.Error(err)
	}

	if len(links) != 1 {
		t.Fatalf("expected 1 link, got %d", len(links))
	}
	if links[0].Size != 23 {
		t.Fatalf("expected size = 23, got %d", links[0].Size)
	}
	if links[0].Name != "name-of-file" {
		t.Fatalf("expected name = name-of-file, got %s", links[0].Name)
	}
	if links[0].Cid.String() != "QmX3qQVKxDGz3URVC3861Z3CKtQKGBn6ffXRBBWGMFz9Lr" {
775
		t.Fatalf("expected cid = QmX3qQVKxDGz3URVC3861Z3CKtQKGBn6ffXRBBWGMFz9Lr, got %s", links[0].Cid)
776 777 778
	}
}

779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825
func TestEntriesExpired(t *testing.T) {
	ctx := context.Background()
	node, api, err := makeAPI(ctx)
	if err != nil {
		t.Error(err)
	}

	r := strings.NewReader("content-of-file")
	k, _, err := coreunix.AddWrapped(node, r, "name-of-file")
	if err != nil {
		t.Error(err)
	}
	parts := strings.Split(k, "/")
	if len(parts) != 2 {
		t.Errorf("unexpected path: %s", k)
	}
	p, err := coreiface.ParsePath("/ipfs/" + parts[0])
	if err != nil {
		t.Error(err)
	}

	ctx, cancel := context.WithCancel(ctx)

	nd, err := api.Unixfs().Get(ctx, p)
	if err != nil {
		t.Error(err)
	}
	cancel()

	it := files.ToDir(nd).Entries()
	if it == nil {
		t.Fatal("it was nil")
	}

	if it.Next() {
		t.Fatal("Next succeeded")
	}

	if it.Err() != context.Canceled {
		t.Fatalf("unexpected error %s", it.Err())
	}

	if it.Next() {
		t.Fatal("Next succeeded")
	}
}

826 827 828 829 830 831 832
func TestLsEmptyDir(t *testing.T) {
	ctx := context.Background()
	node, api, err := makeAPI(ctx)
	if err != nil {
		t.Error(err)
	}

833
	err = node.DAG.Add(ctx, unixfs.EmptyDirNode())
834 835 836 837
	if err != nil {
		t.Error(err)
	}

838
	emptyDir, err := api.Object().New(ctx, options.Object.Type("unixfs-dir"))
839 840 841 842
	if err != nil {
		t.Error(err)
	}

843
	links, err := api.Unixfs().Ls(ctx, coreiface.IpfsPath(emptyDir.Cid()))
844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860
	if err != nil {
		t.Error(err)
	}

	if len(links) != 0 {
		t.Fatalf("expected 0 links, got %d", len(links))
	}
}

// TODO(lgierth) this should test properly, with len(links) > 0
func TestLsNonUnixfs(t *testing.T) {
	ctx := context.Background()
	node, api, err := makeAPI(ctx)
	if err != nil {
		t.Error(err)
	}

861
	nd, err := cbor.WrapObject(map[string]interface{}{"foo": "bar"}, math.MaxUint64, -1)
Jeromy's avatar
Jeromy committed
862 863 864 865
	if err != nil {
		t.Fatal(err)
	}

866
	err = node.DAG.Add(ctx, nd)
867 868 869 870
	if err != nil {
		t.Error(err)
	}

871
	links, err := api.Unixfs().Ls(ctx, coreiface.IpfsPath(nd.Cid()))
872 873 874 875 876 877 878 879
	if err != nil {
		t.Error(err)
	}

	if len(links) != 0 {
		t.Fatalf("expected 0 links, got %d", len(links))
	}
}
880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917

type closeTestF struct {
	files.File
	closed bool

	t *testing.T
}

type closeTestD struct {
	files.Directory
	closed bool

	t *testing.T
}

func (f *closeTestD) Close() error {
	if f.closed {
		f.t.Fatal("already closed")
	}
	f.closed = true
	return nil
}

func (f *closeTestF) Close() error {
	if f.closed {
		f.t.Fatal("already closed")
	}
	f.closed = true
	return nil
}

func TestAddCloses(t *testing.T) {
	ctx := context.Background()
	_, api, err := makeAPI(ctx)
	if err != nil {
		t.Error(err)
	}

Łukasz Magiera's avatar
Łukasz Magiera committed
918 919
	n4 := &closeTestF{files.NewBytesFile([]byte("foo")), false, t}
	d3 := &closeTestD{files.NewMapDirectory(map[string]files.Node{
920 921
		"sub": n4,
	}), false, t}
Łukasz Magiera's avatar
Łukasz Magiera committed
922 923 924
	n2 := &closeTestF{files.NewBytesFile([]byte("bar")), false, t}
	n1 := &closeTestF{files.NewBytesFile([]byte("baz")), false, t}
	d0 := &closeTestD{files.NewMapDirectory(map[string]files.Node{
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949
		"a": d3,
		"b": n1,
		"c": n2,
	}), false, t}

	_, err = api.Unixfs().Add(ctx, d0)
	if err != nil {
		t.Error(err)
	}

	d0.Close() // Adder doesn't close top-level file

	for i, n := range []*closeTestF{n1, n2, n4} {
		if !n.closed {
			t.Errorf("file %d not closed!", i)
		}
	}

	for i, n := range []*closeTestD{d0, d3} {
		if !n.closed {
			t.Errorf("dir %d not closed!", i)
		}
	}

}