flatfs_test.go 13.5 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
	"testing"
12
	"time"
Tommi Virtanen's avatar
Tommi Virtanen committed
13

Jeromy's avatar
Jeromy committed
14 15 16
	"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
17
	"github.com/ipfs/go-ds-flatfs"
Tommi Virtanen's avatar
Tommi Virtanen committed
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
)

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
}

34 35 36 37
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) })
38 39
}

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

44
	fs, err := flatfs.CreateOrOpen(temp, flatfs.Prefix(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
45 46 47 48 49 50 51 52 53 54
	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)
	}
}

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

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

61
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
62 63 64 65 66 67 68 69 70 71
	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)
	}
}

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

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

78
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
	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)
	}
}

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

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

108
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
109 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
	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)
	}
}

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

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

142
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
143 144 145 146 147 148 149 150 151 152
	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)
	}
}

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

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

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

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

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

	seen := false
177
	haveREADME := false
Tommi Virtanen's avatar
Tommi Virtanen committed
178 179 180 181 182 183 184 185 186
	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
187
		case ".", "..", "SHARDING":
Tommi Virtanen's avatar
Tommi Virtanen committed
188
			// ignore
Kevin Atkinson's avatar
Kevin Atkinson committed
189
		case "_README":
190 191 192 193 194
			_, err := ioutil.ReadFile(absPath)
			if err != nil {
				t.Error("could not read _README file")
			}
			haveREADME = true
195
		case p.dir:
Tommi Virtanen's avatar
Tommi Virtanen committed
196
			if !fi.IsDir() {
Kevin Atkinson's avatar
Kevin Atkinson committed
197
				t.Errorf("directory is not a file? %v", fi.Mode())
Tommi Virtanen's avatar
Tommi Virtanen committed
198 199 200 201 202 203 204 205
			}
			// 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())
			}
206 207 208 209
			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
210 211 212 213 214 215 216 217 218 219 220 221
			}
		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")
	}
222
	if fs.ShardStr() == flatfs.IPFS_DEF_SHARD_STR && !haveREADME {
223
		t.Error("expected _README file")
224
	} else if fs.ShardStr() != flatfs.IPFS_DEF_SHARD_STR && haveREADME {
225 226
		t.Error("did not expect _README file")
	}
Tommi Virtanen's avatar
Tommi Virtanen committed
227
}
Tommi Virtanen's avatar
Tommi Virtanen committed
228

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

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

257
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
258 259 260 261 262 263 264 265 266 267 268 269 270
	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)
	}
}

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

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

277
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
	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
294

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

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

301
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
302 303 304 305 306 307 308 309 310 311
	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)
	}
}

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

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

318
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
Tommi Virtanen's avatar
Tommi Virtanen committed
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
	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)
	}
}
338

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

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

345
	fs, err := flatfs.CreateOrOpen(temp, dirFunc(2), false)
346 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
	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
376

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

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

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

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

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

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

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

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

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

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

411
	fun := flatfs.IPFS_DEF_SHARD
412

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

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

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

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

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

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

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

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

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

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

467
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
468
	N := 3200 // should be divisible by 32 so the math works out
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
	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
485
	tolerance := math.Floor(idealFilesPerDir * 0.50)
486
	count := 0
487
	for _, dir := range dirs {
488
		if dir.Name() == flatfs.SHARDING_FN || dir.Name() == flatfs.README_FN {
489 490 491
			continue
		}
		count += 1
492 493 494 495 496 497 498 499 500 501
		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)
		}
	}
502 503 504
	if count != 32 {
		t.Fatalf("Expected 32 directories and one file in %s", tempdir)
	}
505 506
}

Jeromy's avatar
Jeromy committed
507
func BenchmarkConsecutivePut(b *testing.B) {
508
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
Jeromy's avatar
Jeromy committed
509 510 511 512 513 514 515 516 517 518 519 520 521
	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()

522
	fs, err := flatfs.CreateOrOpen(temp, flatfs.Prefix(2), false)
Jeromy's avatar
Jeromy committed
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537
	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) {
538
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
Jeromy's avatar
Jeromy committed
539 540 541 542 543 544 545 546 547 548 549 550 551
	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()

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

	b.ResetTimer()

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

		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)
		}
	}
}