flatfs_test.go 13.4 KB
Newer Older
Tommi Virtanen's avatar
Tommi Virtanen committed
1 2 3
package flatfs_test

import (
Jeromy's avatar
Jeromy committed
4
	"encoding/base32"
Tommi Virtanen's avatar
Tommi Virtanen committed
5
	"io/ioutil"
6
	"math"
7
	"math/rand"
Tommi Virtanen's avatar
Tommi Virtanen committed
8 9
	"os"
	"path/filepath"
10
	"runtime"
Tommi Virtanen's avatar
Tommi Virtanen committed
11 12
	"testing"

Jeromy's avatar
Jeromy committed
13 14 15
	"github.com/ipfs/go-datastore"
	"github.com/ipfs/go-datastore/query"
	dstest "github.com/ipfs/go-datastore/test"
Jakub Sztandera's avatar
Jakub Sztandera committed
16
	"github.com/ipfs/go-ds-flatfs"
Jeromy's avatar
Jeromy committed
17

18
	randbo "github.com/dustin/randbo"
Tommi Virtanen's avatar
Tommi Virtanen committed
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
)

func tempdir(t testing.TB) (path string, cleanup func()) {
	path, err := ioutil.TempDir("", "test-datastore-flatfs-")
	if err != nil {
		t.Fatalf("cannot create temp directory: %v", err)
	}

	cleanup = func() {
		if err := os.RemoveAll(path); err != nil {
			t.Errorf("tempdir cleanup failed: %v", err)
		}
	}
	return path, cleanup
}

35 36 37 38
func tryAllShardFuncs(t *testing.T, testFunc func(mkShardFunc, *testing.T)) {
	t.Run("prefix", func(t *testing.T) { testFunc(flatfs.Prefix, t) })
	t.Run("suffix", func(t *testing.T) { testFunc(flatfs.Suffix, t) })
	t.Run("next-to-last", func(t *testing.T) { testFunc(flatfs.NextToLast, t) })
39 40
}

Tommi Virtanen's avatar
Tommi Virtanen committed
41 42 43 44
func TestPutBadValueType(t *testing.T) {
	temp, cleanup := tempdir(t)
	defer cleanup()

45
	fs, err := flatfs.CreateOrOpen(temp, flatfs.Prefix(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
46 47 48 49 50 51 52 53 54 55
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}

	err = fs.Put(datastore.NewKey("quux"), 22)
	if g, e := err, datastore.ErrInvalidType; g != e {
		t.Fatalf("expected ErrInvalidType, got: %v\n", g)
	}
}

56 57 58
type mkShardFunc func(int) *flatfs.ShardIdV1

func testPut(dirFunc mkShardFunc, t *testing.T) {
Tommi Virtanen's avatar
Tommi Virtanen committed
59 60 61
	temp, cleanup := tempdir(t)
	defer cleanup()

62
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
63 64 65 66 67 68 69 70 71 72
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}

	err = fs.Put(datastore.NewKey("quux"), []byte("foobar"))
	if err != nil {
		t.Fatalf("Put fail: %v\n", err)
	}
}

73
func TestPut(t *testing.T) { tryAllShardFuncs(t, testPut) }
74

75
func testGet(dirFunc mkShardFunc, t *testing.T) {
Tommi Virtanen's avatar
Tommi Virtanen committed
76 77 78
	temp, cleanup := tempdir(t)
	defer cleanup()

79
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}

	const input = "foobar"
	err = fs.Put(datastore.NewKey("quux"), []byte(input))
	if err != nil {
		t.Fatalf("Put fail: %v\n", err)
	}

	data, err := fs.Get(datastore.NewKey("quux"))
	if err != nil {
		t.Fatalf("Get failed: %v", err)
	}
	buf, ok := data.([]byte)
	if !ok {
		t.Fatalf("expected []byte from Get, got %T: %v", data, data)
	}
	if g, e := string(buf), input; g != e {
		t.Fatalf("Get gave wrong content: %q != %q", g, e)
	}
}

103
func TestGet(t *testing.T) { tryAllShardFuncs(t, testGet) }
104

105
func testPutOverwrite(dirFunc mkShardFunc, t *testing.T) {
Tommi Virtanen's avatar
Tommi Virtanen committed
106 107 108
	temp, cleanup := tempdir(t)
	defer cleanup()

109
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}

	const (
		loser  = "foobar"
		winner = "xyzzy"
	)
	err = fs.Put(datastore.NewKey("quux"), []byte(loser))
	if err != nil {
		t.Fatalf("Put fail: %v\n", err)
	}

	err = fs.Put(datastore.NewKey("quux"), []byte(winner))
	if err != nil {
		t.Fatalf("Put fail: %v\n", err)
	}

	data, err := fs.Get(datastore.NewKey("quux"))
	if err != nil {
		t.Fatalf("Get failed: %v", err)
	}
	if g, e := string(data.([]byte)), winner; g != e {
		t.Fatalf("Get gave wrong content: %q != %q", g, e)
	}
}

137
func TestPutOverwrite(t *testing.T) { tryAllShardFuncs(t, testPutOverwrite) }
138

139
func testGetNotFoundError(dirFunc mkShardFunc, t *testing.T) {
Tommi Virtanen's avatar
Tommi Virtanen committed
140 141 142
	temp, cleanup := tempdir(t)
	defer cleanup()

143
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
144 145 146 147 148 149 150 151 152 153
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}

	_, err = fs.Get(datastore.NewKey("quux"))
	if g, e := err, datastore.ErrNotFound; g != e {
		t.Fatalf("expected ErrNotFound, got: %v\n", g)
	}
}

154
func TestGetNotFoundError(t *testing.T) { tryAllShardFuncs(t, testGetNotFoundError) }
155 156

type params struct {
157
	shard *flatfs.ShardIdV1
Kevin Atkinson's avatar
Kevin Atkinson committed
158 159
	dir   string
	key   string
160 161 162
}

func testStorage(p *params, t *testing.T) {
Tommi Virtanen's avatar
Tommi Virtanen committed
163 164 165
	temp, cleanup := tempdir(t)
	defer cleanup()

166
	target := p.dir + string(os.PathSeparator) + p.key + ".data"
167
	fs, err := flatfs.CreateOrOpen(temp, p.shard, false)
Tommi Virtanen's avatar
Tommi Virtanen committed
168 169 170 171
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}

172
	err = fs.Put(datastore.NewKey(p.key), []byte("foobar"))
Tommi Virtanen's avatar
Tommi Virtanen committed
173 174 175 176 177
	if err != nil {
		t.Fatalf("Put fail: %v\n", err)
	}

	seen := false
178
	haveREADME := false
Tommi Virtanen's avatar
Tommi Virtanen committed
179 180 181 182 183 184 185 186 187
	walk := func(absPath string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		path, err := filepath.Rel(temp, absPath)
		if err != nil {
			return err
		}
		switch path {
Kevin Atkinson's avatar
Kevin Atkinson committed
188
		case ".", "..", "SHARDING":
Tommi Virtanen's avatar
Tommi Virtanen committed
189
			// ignore
Kevin Atkinson's avatar
Kevin Atkinson committed
190
		case "_README":
191 192 193 194 195
			_, err := ioutil.ReadFile(absPath)
			if err != nil {
				t.Error("could not read _README file")
			}
			haveREADME = true
196
		case p.dir:
Tommi Virtanen's avatar
Tommi Virtanen committed
197
			if !fi.IsDir() {
Kevin Atkinson's avatar
Kevin Atkinson committed
198
				t.Errorf("directory is not a file? %v", fi.Mode())
Tommi Virtanen's avatar
Tommi Virtanen committed
199 200 201 202 203 204 205 206
			}
			// we know it's there if we see the file, nothing more to
			// do here
		case target:
			seen = true
			if !fi.Mode().IsRegular() {
				t.Errorf("expected a regular file, mode: %04o", fi.Mode())
			}
207 208 209 210
			if runtime.GOOS != "windows" {
				if g, e := fi.Mode()&os.ModePerm&0007, os.FileMode(0000); g != e {
					t.Errorf("file should not be world accessible: %04o", fi.Mode())
				}
Tommi Virtanen's avatar
Tommi Virtanen committed
211 212 213 214 215 216 217 218 219 220 221 222
			}
		default:
			t.Errorf("saw unexpected directory entry: %q %v", path, fi.Mode())
		}
		return nil
	}
	if err := filepath.Walk(temp, walk); err != nil {
		t.Fatal("walk: %v", err)
	}
	if !seen {
		t.Error("did not see the data file")
	}
223
	if fs.ShardStr() == flatfs.IPFS_DEF_SHARD_STR && !haveREADME {
224
		t.Error("expected _README file")
225
	} else if fs.ShardStr() != flatfs.IPFS_DEF_SHARD_STR && haveREADME {
226 227
		t.Error("did not expect _README file")
	}
Tommi Virtanen's avatar
Tommi Virtanen committed
228
}
Tommi Virtanen's avatar
Tommi Virtanen committed
229

230 231 232
func TestStorage(t *testing.T) {
	t.Run("prefix", func(t *testing.T) {
		testStorage(&params{
233
			shard: flatfs.Prefix(2),
Kevin Atkinson's avatar
Kevin Atkinson committed
234 235
			dir:   "qu",
			key:   "quux",
236 237 238 239
		}, t)
	})
	t.Run("suffix", func(t *testing.T) {
		testStorage(&params{
240
			shard: flatfs.Suffix(2),
Kevin Atkinson's avatar
Kevin Atkinson committed
241 242
			dir:   "ux",
			key:   "quux",
243 244
		}, t)
	})
245 246
	t.Run("next-to-last", func(t *testing.T) {
		testStorage(&params{
247
			shard: flatfs.NextToLast(2),
Kevin Atkinson's avatar
Kevin Atkinson committed
248 249
			dir:   "uu",
			key:   "quux",
250 251
		}, t)
	})
252 253
}

254
func testHasNotFound(dirFunc mkShardFunc, t *testing.T) {
Tommi Virtanen's avatar
Tommi Virtanen committed
255 256 257
	temp, cleanup := tempdir(t)
	defer cleanup()

258
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
259 260 261 262 263 264 265 266 267 268 269 270 271
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}

	found, err := fs.Has(datastore.NewKey("quux"))
	if err != nil {
		t.Fatalf("Has fail: %v\n", err)
	}
	if g, e := found, false; g != e {
		t.Fatalf("wrong Has: %v != %v", g, e)
	}
}

272
func TestHasNotFound(t *testing.T) { tryAllShardFuncs(t, testHasNotFound) }
273

274
func testHasFound(dirFunc mkShardFunc, t *testing.T) {
Tommi Virtanen's avatar
Tommi Virtanen committed
275 276 277
	temp, cleanup := tempdir(t)
	defer cleanup()

278
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}
	err = fs.Put(datastore.NewKey("quux"), []byte("foobar"))
	if err != nil {
		t.Fatalf("Put fail: %v\n", err)
	}

	found, err := fs.Has(datastore.NewKey("quux"))
	if err != nil {
		t.Fatalf("Has fail: %v\n", err)
	}
	if g, e := found, true; g != e {
		t.Fatalf("wrong Has: %v != %v", g, e)
	}
}
Tommi Virtanen's avatar
Tommi Virtanen committed
295

296
func TestHasFound(t *testing.T) { tryAllShardFuncs(t, testHasFound) }
297

298
func testDeleteNotFound(dirFunc mkShardFunc, t *testing.T) {
Tommi Virtanen's avatar
Tommi Virtanen committed
299 300 301
	temp, cleanup := tempdir(t)
	defer cleanup()

302
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
303 304 305 306 307 308 309 310 311 312
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}

	err = fs.Delete(datastore.NewKey("quux"))
	if g, e := err, datastore.ErrNotFound; g != e {
		t.Fatalf("expected ErrNotFound, got: %v\n", g)
	}
}

313
func TestDeleteNotFound(t *testing.T) { tryAllShardFuncs(t, testDeleteNotFound) }
314

315
func testDeleteFound(dirFunc mkShardFunc, t *testing.T) {
Tommi Virtanen's avatar
Tommi Virtanen committed
316 317 318
	temp, cleanup := tempdir(t)
	defer cleanup()

319
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}
	err = fs.Put(datastore.NewKey("quux"), []byte("foobar"))
	if err != nil {
		t.Fatalf("Put fail: %v\n", err)
	}

	err = fs.Delete(datastore.NewKey("quux"))
	if err != nil {
		t.Fatalf("Delete fail: %v\n", err)
	}

	// check that it's gone
	_, err = fs.Get(datastore.NewKey("quux"))
	if g, e := err, datastore.ErrNotFound; g != e {
		t.Fatalf("expected Get after Delete to give ErrNotFound, got: %v\n", g)
	}
}
339

340
func TestDeleteFound(t *testing.T) { tryAllShardFuncs(t, testDeleteFound) }
341

342
func testQuerySimple(dirFunc mkShardFunc, t *testing.T) {
343 344 345
	temp, cleanup := tempdir(t)
	defer cleanup()

346
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}
	const myKey = "quux"
	err = fs.Put(datastore.NewKey(myKey), []byte("foobar"))
	if err != nil {
		t.Fatalf("Put fail: %v\n", err)
	}

	res, err := fs.Query(query.Query{KeysOnly: true})
	if err != nil {
		t.Fatalf("Query fail: %v\n", err)
	}
	entries, err := res.Rest()
	if err != nil {
		t.Fatalf("Query Results.Rest fail: %v\n", err)
	}
	seen := false
	for _, e := range entries {
		switch e.Key {
		case datastore.NewKey(myKey).String():
			seen = true
		default:
			t.Errorf("saw unexpected key: %q", e.Key)
		}
	}
	if !seen {
		t.Errorf("did not see wanted key %q in %+v", myKey, entries)
	}
}
Jeromy's avatar
Jeromy committed
377

378
func TestQuerySimple(t *testing.T) { tryAllShardFuncs(t, testQuerySimple) }
379

380
func testBatchPut(dirFunc mkShardFunc, t *testing.T) {
Jeromy's avatar
Jeromy committed
381 382 383
	temp, cleanup := tempdir(t)
	defer cleanup()

384
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Jeromy's avatar
Jeromy committed
385 386 387 388
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}

Jeromy's avatar
Jeromy committed
389 390
	dstest.RunBatchTest(t, fs)
}
Jeromy's avatar
Jeromy committed
391

392
func TestBatchPut(t *testing.T) { tryAllShardFuncs(t, testBatchPut) }
393

394
func testBatchDelete(dirFunc mkShardFunc, t *testing.T) {
Jeromy's avatar
Jeromy committed
395 396
	temp, cleanup := tempdir(t)
	defer cleanup()
Jeromy's avatar
Jeromy committed
397

398
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Jeromy's avatar
Jeromy committed
399
	if err != nil {
Jeromy's avatar
Jeromy committed
400
		t.Fatalf("New fail: %v\n", err)
Jeromy's avatar
Jeromy committed
401 402
	}

Jeromy's avatar
Jeromy committed
403
	dstest.RunBatchDeleteTest(t, fs)
Jeromy's avatar
Jeromy committed
404 405
}

406
func TestBatchDelete(t *testing.T) { tryAllShardFuncs(t, testBatchDelete) }
407

408 409 410 411
func TestSHARDINGFile(t *testing.T) {
	tempdir, cleanup := tempdir(t)
	defer cleanup()

412
	fun := flatfs.IPFS_DEF_SHARD
413

414
	err := flatfs.Create(tempdir, fun)
415
	if err != nil {
416
		t.Fatalf("Create: %v\n", err)
417 418
	}

419
	fs, err := flatfs.Open(tempdir, false)
420
	if err != nil {
421
		t.Fatalf("Open fail: %v\n", err)
422
	}
423 424
	if fs.ShardStr() != flatfs.IPFS_DEF_SHARD_STR {
		t.Fatalf("Expected '%s' for shard function got '%s'", flatfs.IPFS_DEF_SHARD_STR, fs.ShardStr())
425 426 427
	}
	fs.Close()

428
	fs, err = flatfs.CreateOrOpen(tempdir, fun, false)
429 430 431 432 433
	if err != nil {
		t.Fatalf("Could not reopen repo: %v\n", err)
	}
	fs.Close()

434
	fs, err = flatfs.CreateOrOpen(tempdir, flatfs.Prefix(5), false)
435 436 437 438 439
	if err == nil {
		t.Fatalf("Was able to open repo with incompatible sharding function")
	}
}

440
func TestInvalidPrefix(t *testing.T) {
441
	_, err := flatfs.ParseShardFunc("/bad/prefix/v1/next-to-last/2")
442
	if err == nil {
443
		t.Fatalf("Expected an error while parsing a shard identifier with a bad prefix")
444 445 446
	}
}

447 448 449 450 451 452
func TestNonDatastoreDir(t *testing.T) {
	tempdir, cleanup := tempdir(t)
	defer cleanup()

	ioutil.WriteFile(filepath.Join(tempdir, "afile"), []byte("Some Content"), 0644)

453
	err := flatfs.Create(tempdir, flatfs.NextToLast(2))
454 455 456 457 458
	if err == nil {
		t.Fatalf("Expected an error when creating a datastore in a non-empty directory")
	}
}

459 460 461 462
func TestNoCluster(t *testing.T) {
	tempdir, cleanup := tempdir(t)
	defer cleanup()

463
	fs, err := flatfs.CreateOrOpen(tempdir, flatfs.NextToLast(1), false)
464 465 466 467
	if err != nil {
		t.Fatalf("New fail: %v\n", err)
	}

468 469
	r := randbo.NewFrom(rand.NewSource(0))
	N := 3200 // should be divisible by 32 so the math works out
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
	for i := 0; i < N; i++ {
		blk := make([]byte, 1000)
		r.Read(blk)

		key := "CIQ" + base32.StdEncoding.EncodeToString(blk[:10])
		err := fs.Put(datastore.NewKey(key), blk)
		if err != nil {
			t.Fatalf("Put fail: %v\n", err)
		}
	}

	dirs, err := ioutil.ReadDir(tempdir)
	if err != nil {
		t.Fatalf("ReadDir fail: %v\n", err)
	}
	idealFilesPerDir := float64(N) / 32.0
486
	tolerance := math.Floor(idealFilesPerDir * 0.20)
487
	count := 0
488
	for _, dir := range dirs {
489
		if dir.Name() == flatfs.SHARDING_FN || dir.Name() == flatfs.README_FN {
490 491 492
			continue
		}
		count += 1
493 494 495 496 497 498 499 500 501 502
		files, err := ioutil.ReadDir(filepath.Join(tempdir, dir.Name()))
		if err != nil {
			t.Fatalf("ReadDir fail: %v\n", err)
		}
		num := float64(len(files))
		if math.Abs(num-idealFilesPerDir) > tolerance {
			t.Fatalf("Dir %s has %.0f files, expected between %.f and %.f files",
				filepath.Join(tempdir, dir.Name()), num, idealFilesPerDir-tolerance, idealFilesPerDir+tolerance)
		}
	}
503 504 505
	if count != 32 {
		t.Fatalf("Expected 32 directories and one file in %s", tempdir)
	}
506 507
}

Jeromy's avatar
Jeromy committed
508
func BenchmarkConsecutivePut(b *testing.B) {
509
	r := randbo.New()
Jeromy's avatar
Jeromy committed
510 511 512 513 514 515 516 517 518 519 520 521 522
	var blocks [][]byte
	var keys []datastore.Key
	for i := 0; i < b.N; i++ {
		blk := make([]byte, 256*1024)
		r.Read(blk)
		blocks = append(blocks, blk)

		key := base32.StdEncoding.EncodeToString(blk[:8])
		keys = append(keys, datastore.NewKey(key))
	}
	temp, cleanup := tempdir(b)
	defer cleanup()

523
	fs, err := flatfs.CreateOrOpen(temp, flatfs.Prefix(2), false)
Jeromy's avatar
Jeromy committed
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
	if err != nil {
		b.Fatalf("New fail: %v\n", err)
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		err := fs.Put(keys[i], blocks[i])
		if err != nil {
			b.Fatal(err)
		}
	}
}

func BenchmarkBatchedPut(b *testing.B) {
539
	r := randbo.New()
Jeromy's avatar
Jeromy committed
540 541 542 543 544 545 546 547 548 549 550 551 552
	var blocks [][]byte
	var keys []datastore.Key
	for i := 0; i < b.N; i++ {
		blk := make([]byte, 256*1024)
		r.Read(blk)
		blocks = append(blocks, blk)

		key := base32.StdEncoding.EncodeToString(blk[:8])
		keys = append(keys, datastore.NewKey(key))
	}
	temp, cleanup := tempdir(b)
	defer cleanup()

553
	fs, err := flatfs.CreateOrOpen(temp, flatfs.Prefix(2), false)
Jeromy's avatar
Jeromy committed
554 555 556 557 558 559 560
	if err != nil {
		b.Fatalf("New fail: %v\n", err)
	}

	b.ResetTimer()

	for i := 0; i < b.N; {
Jeromy's avatar
Jeromy committed
561 562 563 564
		batch, err := fs.Batch()
		if err != nil {
			b.Fatal(err)
		}
Jeromy's avatar
Jeromy committed
565 566 567 568 569 570 571 572 573 574 575 576 577

		for n := i; i-n < 512 && i < b.N; i++ {
			err := batch.Put(keys[i], blocks[i])
			if err != nil {
				b.Fatal(err)
			}
		}
		err = batch.Commit()
		if err != nil {
			b.Fatal(err)
		}
	}
}