From 1ea24f80dccf07ae114858ff7f43cb33d7c05632 Mon Sep 17 00:00:00 2001 From: cpb8010 Date: Mon, 16 Sep 2013 00:06:48 -0400 Subject: [PATCH] Completed cache.Tx tests and added benchmarks --- cache/redis/redis.go | 67 +++--- cache/redis/redis_bench_test.go | 336 +++++++++++++++++++---------- cache/redis/redis_test.go | 68 +++--- cache/redis/tx_test.go | 367 +++++++++++++++++++------------- models/models.go | 8 + server/announce.go | 4 +- 6 files changed, 527 insertions(+), 323 deletions(-) diff --git a/cache/redis/redis.go b/cache/redis/redis.go index 90e3800..dd3d245 100644 --- a/cache/redis/redis.go +++ b/cache/redis/redis.go @@ -89,9 +89,8 @@ func (p *Pool) Get() (cache.Tx, error) { } type Tx struct { - conf *config.DataStore - done bool - multi bool + conf *config.DataStore + done bool redis.Conn } @@ -107,7 +106,6 @@ func createUser(userVals []string) (*models.User, error) { if len(userVals) != 7 { return nil, ErrCreateUser } - // This could be a loop+switch ID, err := strconv.ParseUint(userVals[0], 10, 64) if err != nil { return nil, err @@ -133,7 +131,8 @@ func createUser(userVals []string) (*models.User, error) { if err != nil { return nil, err } - return &models.User{ID, Passkey, UpMultiplier, DownMultiplier, Slots, SlotsUsed, uint(Snatches)}, nil + return &models.User{ID: ID, Passkey: Passkey, UpMultiplier: UpMultiplier, + DownMultiplier: DownMultiplier, Slots: Slots, SlotsUsed: SlotsUsed, Snatches: uint(Snatches)}, nil } // This is a mulple action command, it's not internally atomic @@ -175,7 +174,8 @@ func (tx *Tx) createTorrent(torrentVals []string) (*models.Torrent, error) { return nil, err } - return &models.Torrent{ID, Infohash, Active, seeders, leechers, uint(Snatches), UpMultiplier, DownMultiplier, LastAction}, nil + return &models.Torrent{ID: ID, Infohash: Infohash, Active: Active, Seeders: seeders, Leechers: leechers, + Snatches: uint(Snatches), UpMultiplier: UpMultiplier, DownMultiplier: DownMultiplier, LastAction: LastAction}, nil } // The peer hashkey relies on the combination of peerID, userID, and torrentID being unique @@ -220,7 +220,7 @@ func (tx *Tx) removePeers(torrentID uint64, peers map[string]models.Peer, peerTy } delete(peers, peer.ID) } - // Only delete the set if all the peer deletions were successful + // Will only delete the set if all the peer deletions were successful setKey := tx.conf.Prefix + peerTypePrefix + strconv.FormatUint(torrentID, 36) _, err := tx.Do("DEL", setKey) if err != nil { @@ -234,8 +234,8 @@ func getPeerHashKey(peer *models.Peer) string { return peer.ID + ":" + strconv.FormatUint(peer.UserID, 36) + ":" + strconv.FormatUint(peer.TorrentID, 36) } -func getPeerSetKey(prefix string, peer *models.Peer) string { - return prefix + strconv.FormatUint(peer.TorrentID, 36) +func getPeerSetKey(typePrefix string, peer *models.Peer) string { + return typePrefix + strconv.FormatUint(peer.TorrentID, 36) } // This is a mulple action command, it's not internally atomic @@ -286,7 +286,8 @@ func createPeer(peerVals []string) (*models.Peer, error) { if err != nil { return nil, err } - return &models.Peer{ID, UserID, TorrentID, IP, Port, Uploaded, Downloaded, Left, LastAnnounce}, nil + return &models.Peer{ID: ID, UserID: UserID, TorrentID: TorrentID, IP: IP, Port: Port, + Uploaded: Uploaded, Downloaded: Downloaded, Left: Left, LastAnnounce: LastAnnounce}, nil } @@ -309,7 +310,7 @@ func (tx *Tx) getPeers(torrentID uint64, peerTypePrefix string) (peers map[strin if err != nil { return nil, err } - peers[peer.ID] = *peer + peers[models.PeerMapKey(peer)] = *peer } return } @@ -356,7 +357,6 @@ func (tx *Tx) RemoveTorrent(t *models.Torrent) error { if err != nil { return err } - return nil } @@ -438,14 +438,14 @@ func (tx *Tx) UnWhitelistClient(peerID string) error { func (tx *Tx) RecordSnatch(user *models.User, torrent *models.Torrent) error { torrentKey := tx.conf.Prefix + TorrentPrefix + torrent.Infohash - snatchCount, err := redis.Int(tx.Do("HINCRBY", torrentKey, 1)) + snatchCount, err := redis.Int(tx.Do("HINCRBY", torrentKey, "snatches", 1)) if err != nil { return err } torrent.Snatches = uint(snatchCount) - userKey := tx.conf.Prefix + TorrentPrefix + torrent.Infohash - snatchCount, err = redis.Int(tx.Do("HINCRBY", userKey, 1)) + userKey := tx.conf.Prefix + UserPrefix + user.Passkey + snatchCount, err = redis.Int(tx.Do("HINCRBY", userKey, "snatches", 1)) if err != nil { return err } @@ -455,10 +455,25 @@ func (tx *Tx) RecordSnatch(user *models.User, torrent *models.Torrent) error { func (tx *Tx) MarkActive(torrent *models.Torrent) error { hashkey := tx.conf.Prefix + TorrentPrefix + torrent.Infohash - activeExists, err := redis.Int(tx.Do("HSET", hashkey, true)) + activeExists, err := redis.Int(tx.Do("HSET", hashkey, "active", true)) if err != nil { return err } + torrent.Active = true + // HSET returns 1 if hash didn't exist before + if activeExists == 1 { + return ErrMarkActive + } + return nil +} + +func (tx *Tx) MarkInActive(torrent *models.Torrent) error { + hashkey := tx.conf.Prefix + TorrentPrefix + torrent.Infohash + activeExists, err := redis.Int(tx.Do("HSET", hashkey, "active", false)) + if err != nil { + return err + } + torrent.Active = false // HSET returns 1 if hash didn't exist before if activeExists == 1 { return ErrMarkActive @@ -480,7 +495,7 @@ func (tx *Tx) AddLeecher(torrent *models.Torrent, peer *models.Peer) error { if torrent.Leechers == nil { torrent.Leechers = make(map[string]models.Peer) } - torrent.Leechers[peer.ID] = *peer + torrent.Leechers[models.PeerMapKey(peer)] = *peer return nil } @@ -491,7 +506,7 @@ func (tx *Tx) SetLeecher(t *models.Torrent, p *models.Peer) error { if err != nil { return err } - t.Leechers[p.ID] = *p + t.Leechers[models.PeerMapKey(p)] = *p return nil } @@ -500,7 +515,7 @@ func (tx *Tx) RemoveLeecher(t *models.Torrent, p *models.Peer) error { if err != nil { return err } - delete(t.Leechers, p.ID) + delete(t.Leechers, models.PeerMapKey(p)) return nil } @@ -513,8 +528,8 @@ func (tx *Tx) LeecherFinished(torrent *models.Torrent, peer *models.Peer) error if err != nil { return err } - torrent.Seeders[peer.ID] = *peer - delete(torrent.Leechers, peer.ID) + torrent.Seeders[models.PeerMapKey(peer)] = *peer + delete(torrent.Leechers, models.PeerMapKey(peer)) err = tx.setPeer(peer) return err @@ -534,7 +549,7 @@ func (tx *Tx) AddSeeder(torrent *models.Torrent, peer *models.Peer) error { if torrent.Seeders == nil { torrent.Seeders = make(map[string]models.Peer) } - torrent.Seeders[peer.ID] = *peer + torrent.Seeders[models.PeerMapKey(peer)] = *peer return nil } @@ -543,7 +558,7 @@ func (tx *Tx) SetSeeder(t *models.Torrent, p *models.Peer) error { if err != nil { return err } - t.Seeders[p.ID] = *p + t.Seeders[models.PeerMapKey(p)] = *p return nil } @@ -552,13 +567,13 @@ func (tx *Tx) RemoveSeeder(t *models.Torrent, p *models.Peer) error { if err != nil { return err } - delete(t.Seeders, p.ID) + delete(t.Seeders, models.PeerMapKey(p)) return nil } func (tx *Tx) IncrementSlots(u *models.User) error { hashkey := tx.conf.Prefix + UserPrefix + u.Passkey - slotCount, err := redis.Int(tx.Do("HINCRBY", hashkey, 1)) + slotCount, err := redis.Int(tx.Do("HINCRBY", hashkey, "slots", 1)) if err != nil { return err } @@ -568,7 +583,7 @@ func (tx *Tx) IncrementSlots(u *models.User) error { func (tx *Tx) DecrementSlots(u *models.User) error { hashkey := tx.conf.Prefix + UserPrefix + u.Passkey - slotCount, err := redis.Int(tx.Do("HINCRBY", hashkey, -1)) + slotCount, err := redis.Int(tx.Do("HINCRBY", hashkey, "slots", -1)) if err != nil { return err } diff --git a/cache/redis/redis_bench_test.go b/cache/redis/redis_bench_test.go index 7c14bee..61bd0da 100644 --- a/cache/redis/redis_bench_test.go +++ b/cache/redis/redis_bench_test.go @@ -6,128 +6,240 @@ package redis import ( - "errors" + "math/rand" "testing" - - "github.com/garyburd/redigo/redis" - - "github.com/pushrax/chihaya/models" + "time" ) -var ( - ErrTxDone = errors.New("cache: Transaction has already been committed or rolled back") - ErrTxConflict = errors.New("cache: Commit interrupted, update transaction and repeat") -) - -// Maximum number of parallel retries; depends on system latency -const MAX_RETRIES = 9000 - -// Legacy JSON support for benching -func (tx *Tx) initiateWrite() error { - if tx.done { - return ErrTxDone - } - if tx.multi != true { - tx.multi = true - return tx.Send("MULTI") - } - return nil -} - -func (tx *Tx) initiateRead() error { - if tx.done { - return ErrTxDone - } - if tx.multi == true { - panic("Tried to read during MULTI") - } - return nil -} - -func (tx *Tx) Commit() error { - if tx.done { - return ErrTxDone - } - if tx.multi == true { - execResponse, err := tx.Do("EXEC") - if execResponse == nil { - tx.multi = false - return ErrTxConflict - } - if err != nil { - return err - } - } - tx.close() - return nil -} - -func (tx *Tx) Rollback() error { - if tx.done { - return ErrTxDone - } - // Undoes watches and multi - if _, err := tx.Do("DISCARD"); err != nil { - return err - } - tx.multi = false - tx.close() - return nil -} - -func ExampleRedisTypesSchemaFindUser(passkey string, t TestReporter) (*models.User, bool) { - testTx := createTestTxObj(t) - hashkey := testTx.conf.Prefix + UserPrefix + passkey - userVals, err := redis.Strings(testTx.Do("HVALS", hashkey)) - if len(userVals) == 0 { - return nil, false - } - verifyErrNil(err, t) - compareUser, err := createUser(userVals) - verifyErrNil(err, t) - return compareUser, true -} - -func BenchmarkRedisTypesSchemaRemoveSeeder(b *testing.B) { - for bCount := 0; bCount < b.N; bCount++ { - //TODO this needs to be updated - b.Error("Unimplemented") - - } -} - -func BenchmarkRedisTypesSchemaFindUser(b *testing.B) { - - // Ensure successful user find ( a failed lookup may have different performance ) +func BenchmarkSuccessfulFindUser(b *testing.B) { b.StopTimer() + tx := createTestTx() testUser := createTestUser() - testTx := createTestTxObj(b) - hashkey := testTx.conf.Prefix + UserPrefix + testUser.Passkey - reply, err := testTx.Do("HMSET", hashkey, - "id", testUser.ID, - "passkey", testUser.Passkey, - "up_multiplier", testUser.UpMultiplier, - "down_multiplier", testUser.DownMultiplier, - "slots", testUser.Slots, - "slots_used", testUser.SlotsUsed) - - if reply == nil { - b.Log("no hash fields added!") - } - verifyErrNil(err, b) + panicErrNil(tx.AddUser(testUser)) b.StartTimer() for bCount := 0; bCount < b.N; bCount++ { - compareUser, exists := ExampleRedisTypesSchemaFindUser(testUser.Passkey, b) - - b.StopTimer() - if !exists { - b.Error("User not found!") + foundUser, found, err := tx.FindUser(testUser.Passkey) + panicErrNil(err) + if !found { + b.Error("user not found", testUser) } - if testUser != *compareUser { - b.Errorf("user mismatch: %v vs. %v", compareUser, testUser) + if *foundUser != *testUser { + b.Error("found user mismatch", *foundUser, testUser) } - b.StartTimer() } } + +func BenchmarkFailedFindUser(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testUser := createTestUser() + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + + _, found, err := tx.FindUser(testUser.Passkey) + panicErrNil(err) + if found { + b.Error("user not found", testUser) + } + } +} + +func BenchmarkSuccessfulFindTorrent(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testTorrent := createTestTorrent() + + panicErrNil(tx.AddTorrent(testTorrent)) + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + foundTorrent, found, err := tx.FindTorrent(testTorrent.Infohash) + panicErrNil(err) + if !found { + b.Error("torrent not found", testTorrent) + } + // Incomplete comparison as maps make struct not nativly comparable + if foundTorrent.Infohash != testTorrent.Infohash { + b.Error("found torrent mismatch", foundTorrent, testTorrent) + } + } +} + +func BenchmarkFailFindTorrent(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testTorrent := createTestTorrent() + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + foundTorrent, found, err := tx.FindTorrent(testTorrent.Infohash) + panicErrNil(err) + if found { + b.Error("torrent found", foundTorrent) + } + } +} + +func BenchmarkSuccessfulClientWhitelisted(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testPeerID := "-lt0D30-" + panicErrNil(tx.WhitelistClient(testPeerID)) + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + found, err := tx.ClientWhitelisted(testPeerID) + panicErrNil(err) + if !found { + b.Error("peerID not found", testPeerID) + } + } +} + +func BenchmarkFailClientWhitelisted(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testPeerID2 := "TIX0192" + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + found, err := tx.ClientWhitelisted(testPeerID2) + panicErrNil(err) + if found { + b.Error("peerID found", testPeerID2) + } + } +} + +func BenchmarkRecordSnatch(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testTorrent := createTestTorrent() + testUser := createTestUser() + panicErrNil(tx.AddTorrent(testTorrent)) + panicErrNil(tx.AddUser(testUser)) + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + panicErrNil(tx.RecordSnatch(testUser, testTorrent)) + } +} + +func BenchmarkMarkActive(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testTorrent := createTestTorrent() + testTorrent.Active = false + panicErrNil(tx.AddTorrent(testTorrent)) + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + panicErrNil(tx.MarkActive(testTorrent)) + } +} + +func BenchmarkAddSeeder(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + b.StopTimer() + testSeeder := createTestPeer(createTestUserID(), testTorrent.ID) + b.StartTimer() + + panicErrNil(tx.AddSeeder(testTorrent, testSeeder)) + } +} + +func BenchmarkRemoveSeeder(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + testSeeder := createTestPeer(createTestUserID(), testTorrent.ID) + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + b.StopTimer() + tx.AddSeeder(testTorrent, testSeeder) + b.StartTimer() + + panicErrNil(tx.RemoveSeeder(testTorrent, testSeeder)) + } +} + +func BenchmarkSetSeeder(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + testSeeder := createTestPeer(createTestUserID(), testTorrent.ID) + panicErrNil(tx.AddSeeder(testTorrent, testSeeder)) + r := rand.New(rand.NewSource(time.Now().UnixNano())) + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + b.StopTimer() + testSeeder.Uploaded += uint64(r.Int63()) + b.StartTimer() + + tx.SetSeeder(testTorrent, testSeeder) + } +} + +func BenchmarkIncrementSlots(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testUser := createTestUser() + panicErrNil(tx.AddUser(testUser)) + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + panicErrNil(tx.IncrementSlots(testUser)) + } +} + +func BenchmarkLeecherFinished(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + b.StopTimer() + testLeecher := createTestPeer(createTestUserID(), testTorrent.ID) + panicErrNil(tx.AddLeecher(testTorrent, testLeecher)) + testLeecher.Left = 0 + b.StartTimer() + + panicErrNil(tx.LeecherFinished(testTorrent, testLeecher)) + } +} + +// This is a comparision to the Leecher finished function +func BenchmarkRemoveLeecherAddSeeder(b *testing.B) { + b.StopTimer() + tx := createTestTx() + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + b.StartTimer() + + for bCount := 0; bCount < b.N; bCount++ { + b.StopTimer() + testLeecher := createTestPeer(createTestUserID(), testTorrent.ID) + panicErrNil(tx.AddLeecher(testTorrent, testLeecher)) + testLeecher.Left = 0 + b.StartTimer() + + panicErrNil(tx.RemoveLeecher(testTorrent, testLeecher)) + panicErrNil(tx.AddSeeder(testTorrent, testLeecher)) + } + +} diff --git a/cache/redis/redis_test.go b/cache/redis/redis_test.go index 777cfa8..b78aac1 100644 --- a/cache/redis/redis_test.go +++ b/cache/redis/redis_test.go @@ -6,6 +6,7 @@ package redis import ( "crypto/rand" + "fmt" "io" "os" "strconv" @@ -64,16 +65,17 @@ type TestReporter interface { Logf(format string, args ...interface{}) } -func verifyErrNil(err error, t TestReporter) { +func panicErrNil(err error) { if err != nil { - t.Error(err) + fmt.Println(err) + panic(err) } } -func createTestTxObj(t TestReporter) *Tx { +func createTestTxObj() *Tx { testConfig, err := config.Open(os.Getenv("TESTCONFIGPATH")) conf := &testConfig.Cache - verifyErrNil(err, t) + panicErrNil(err) testPool := &Pool{ conf: conf, @@ -86,27 +88,27 @@ func createTestTxObj(t TestReporter) *Tx { } txObj := &Tx{ - conf: testPool.conf, - done: false, - multi: false, - Conn: testPool.pool.Get(), + conf: testPool.conf, + done: false, + Conn: testPool.pool.Get(), } - verifyErrNil(err, t) + panicErrNil(err) // Test connection before returning _, err = txObj.Do("PING") - verifyErrNil(err, t) + panicErrNil(err) return txObj } -func createTestUser() models.User { - testUser := models.User{createTestUserID(), createTestPasskey(), 1.01, 1.0, 4, 2, 7} - return testUser +func createTestUser() *models.User { + return &models.User{ID: createTestUserID(), Passkey: createTestPasskey(), + UpMultiplier: 1.01, DownMultiplier: 1.0, Slots: 4, SlotsUsed: 2, Snatches: 7} } func createTestPeer(userID uint64, torrentID uint64) *models.Peer { - return &models.Peer{createTestPeerID(), userID, torrentID, "127.0.0.1", 6889, 1024, 3000, 4200, 11} + return &models.Peer{ID: createTestPeerID(), UserID: userID, TorrentID: torrentID, + IP: "127.0.0.1", Port: 6889, Uploaded: 1024, Downloaded: 3000, Left: 4200, LastAnnounce: 11} } func createTestPeers(torrentID uint64, num int) map[string]models.Peer { @@ -126,36 +128,22 @@ func createTestTorrent() *models.Torrent { testSeeders := createTestPeers(torrentID, 4) testLeechers := createTestPeers(torrentID, 2) - testTorrent := models.Torrent{torrentID, torrentInfohash, true, testSeeders, testLeechers, 11, 0.0, 0.0, 0} + testTorrent := models.Torrent{ID: torrentID, Infohash: torrentInfohash, Active: true, + Seeders: testSeeders, Leechers: testLeechers, Snatches: 11, UpMultiplier: 1.0, DownMultiplier: 1.0, LastAction: 0} return &testTorrent } -func TestAddGetPeers(t *testing.T) { +func TestPeersAlone(t *testing.T) { - testTx := createTestTxObj(t) - testTorrent := createTestTorrent() + testTx := createTestTxObj() + testTorrentID := createTestTorrentID() + testPeers := createTestPeers(testTorrentID, 3) - setkey := testTx.conf.Prefix + SeederPrefix + strconv.FormatUint(testTorrent.ID, 36) - testTx.Do("DEL", setkey) - - testTx.addPeers(testTorrent.Seeders, SeederPrefix) - peerMap, err := testTx.getPeers(testTorrent.ID, SeederPrefix) - if err != nil { - t.Error(err) - } else if len(peerMap) != len(testTorrent.Seeders) { - t.Error("Num Peers not equal") + panicErrNil(testTx.addPeers(testPeers, "test:")) + peerMap, err := testTx.getPeers(testTorrentID, "test:") + panicErrNil(err) + if len(peerMap) != len(testPeers) { + t.Error("Num Peers not equal ", len(peerMap), len(testPeers)) } -} - -func TestCloseClosedTransaction(t *testing.T) { - //require panic - defer func() { - if err := recover(); err == nil { - t.Error("Closing a closed transaction did not panic") - } - }() - - testTx := createTestTxObj(t) - testTx.close() - testTx.close() + panicErrNil(testTx.removePeers(testTorrentID, testPeers, "test:")) } diff --git a/cache/redis/tx_test.go b/cache/redis/tx_test.go index f5d50c9..76ff5ac 100644 --- a/cache/redis/tx_test.go +++ b/cache/redis/tx_test.go @@ -5,7 +5,6 @@ package redis import ( - "fmt" "math/rand" "os" "testing" @@ -13,15 +12,9 @@ import ( "github.com/pushrax/chihaya/cache" "github.com/pushrax/chihaya/config" + "github.com/pushrax/chihaya/models" ) -func panicErrNil(err error) { - if err != nil { - fmt.Println(err) - panic(err) - } -} - func createTestTx() cache.Tx { testConfig, err := config.Open(os.Getenv("TESTCONFIGPATH")) panicErrNil(err) @@ -38,24 +31,24 @@ func createTestTx() cache.Tx { func TestFindUserSuccess(t *testing.T) { tx := createTestTx() - testUser1 := createTestUser() + testUser := createTestUser() - panicErrNil(tx.AddUser(&testUser1)) - foundUser, found, err := tx.FindUser(testUser1.Passkey) + panicErrNil(tx.AddUser(testUser)) + foundUser, found, err := tx.FindUser(testUser.Passkey) panicErrNil(err) if !found { - t.Error("user not found", testUser1) + t.Error("user not found", testUser) } - if *foundUser != testUser1 { - t.Error("found user mismatch", *foundUser, testUser1) + if *foundUser != *testUser { + t.Error("found user mismatch", *foundUser, testUser) } } func TestFindUserFail(t *testing.T) { tx := createTestTx() - testUser2 := createTestUser() + testUser := createTestUser() - foundUser, found, err := tx.FindUser(testUser2.Passkey) + foundUser, found, err := tx.FindUser(testUser.Passkey) panicErrNil(err) if found { t.Error("user found", foundUser) @@ -64,12 +57,12 @@ func TestFindUserFail(t *testing.T) { func TestRemoveUser(t *testing.T) { tx := createTestTx() - testUser1 := createTestUser() + testUser := createTestUser() - panicErrNil(tx.AddUser(&testUser1)) - err := tx.RemoveUser(&testUser1) + panicErrNil(tx.AddUser(testUser)) + err := tx.RemoveUser(testUser) panicErrNil(err) - foundUser, found, err := tx.FindUser(testUser1.Passkey) + foundUser, found, err := tx.FindUser(testUser.Passkey) panicErrNil(err) if found { t.Error("removed user found", foundUser) @@ -78,25 +71,25 @@ func TestRemoveUser(t *testing.T) { func TestFindTorrent(t *testing.T) { tx := createTestTx() - testTorrent1 := createTestTorrent() + testTorrent := createTestTorrent() - panicErrNil(tx.AddTorrent(testTorrent1)) - foundTorrent, found, err := tx.FindTorrent(testTorrent1.Infohash) + panicErrNil(tx.AddTorrent(testTorrent)) + foundTorrent, found, err := tx.FindTorrent(testTorrent.Infohash) panicErrNil(err) if !found { - t.Error("torrent not found", testTorrent1) + t.Error("torrent not found", testTorrent) } // Incomplete comparison as maps make struct not nativly comparable - if foundTorrent.Infohash != testTorrent1.Infohash { - t.Error("found torrent mismatch", foundTorrent, testTorrent1) + if foundTorrent.Infohash != testTorrent.Infohash { + t.Error("found torrent mismatch", foundTorrent, testTorrent) } } func TestFindTorrentFail(t *testing.T) { tx := createTestTx() - testTorrent2 := createTestTorrent() + testTorrent := createTestTorrent() - foundTorrent, found, err := tx.FindTorrent(testTorrent2.Infohash) + foundTorrent, found, err := tx.FindTorrent(testTorrent.Infohash) panicErrNil(err) if found { t.Error("torrent found", foundTorrent) @@ -105,11 +98,11 @@ func TestFindTorrentFail(t *testing.T) { func TestRemoveTorrent(t *testing.T) { tx := createTestTx() - testTorrent1 := createTestTorrent() - panicErrNil(tx.AddTorrent(testTorrent1)) + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) - panicErrNil(tx.RemoveTorrent(testTorrent1)) - foundTorrent, found, err := tx.FindTorrent(testTorrent1.Infohash) + panicErrNil(tx.RemoveTorrent(testTorrent)) + foundTorrent, found, err := tx.FindTorrent(testTorrent.Infohash) panicErrNil(err) if found { t.Error("removed torrent found", foundTorrent) @@ -118,13 +111,13 @@ func TestRemoveTorrent(t *testing.T) { func TestClientWhitelistSuccess(t *testing.T) { tx := createTestTx() - testPeerID1 := "-lt0D30-" + testPeerID := "-lt0D30-" - panicErrNil(tx.WhitelistClient(testPeerID1)) - found, err := tx.ClientWhitelisted(testPeerID1) + panicErrNil(tx.WhitelistClient(testPeerID)) + found, err := tx.ClientWhitelisted(testPeerID) panicErrNil(err) if !found { - t.Error("peerID not found", testPeerID1) + t.Error("peerID not found", testPeerID) } } @@ -137,194 +130,282 @@ func TestClientWhitelistFail(t *testing.T) { if found { t.Error("peerID found", testPeerID2) } +} +func TestRecordSnatch(t *testing.T) { + tx := createTestTx() + testTorrent := createTestTorrent() + testUser := createTestUser() + panicErrNil(tx.AddTorrent(testTorrent)) + panicErrNil(tx.AddUser(testUser)) + + userSnatches := testUser.Snatches + torrentSnatches := testTorrent.Snatches + + panicErrNil(tx.RecordSnatch(testUser, testTorrent)) + + foundTorrent, _, err := tx.FindTorrent(testTorrent.Infohash) + panicErrNil(err) + foundUser, _, err := tx.FindUser(testUser.Passkey) + panicErrNil(err) + + if testUser.Snatches != userSnatches+1 { + t.Error("snatch not recorded to local user", testUser.Snatches, userSnatches+1) + } + if testTorrent.Snatches != torrentSnatches+1 { + t.Error("snatch not recorded to local torrent") + } + if foundUser.Snatches != userSnatches+1 { + t.Error("snatch not recorded to cached user", foundUser.Snatches, userSnatches+1) + } + if foundTorrent.Snatches != torrentSnatches+1 { + t.Error("snatch not recorded to cached torrent") + } +} + +func TestMarkActive(t *testing.T) { + tx := createTestTx() + testTorrent := createTestTorrent() + testTorrent.Active = false + panicErrNil(tx.AddTorrent(testTorrent)) + + panicErrNil(tx.MarkActive(testTorrent)) + foundTorrent, _, err := tx.FindTorrent(testTorrent.Infohash) + panicErrNil(err) + + if foundTorrent.Active != true { + t.Error("cached torrent not activated") + } + if testTorrent.Active != true { + t.Error("cached torrent not activated") + } } func TestClientWhitelistRemove(t *testing.T) { tx := createTestTx() - testPeerID1 := "-lt0D30-" - panicErrNil(tx.WhitelistClient(testPeerID1)) - panicErrNil(tx.UnWhitelistClient(testPeerID1)) + testPeerID := "-lt0D30-" + panicErrNil(tx.WhitelistClient(testPeerID)) + panicErrNil(tx.UnWhitelistClient(testPeerID)) - found, err := tx.ClientWhitelisted(testPeerID1) + found, err := tx.ClientWhitelisted(testPeerID) panicErrNil(err) if found { - t.Error("removed peerID found", testPeerID1) + t.Error("removed peerID found", testPeerID) } } func TestAddSeeder(t *testing.T) { tx := createTestTx() - testTorrent1 := createTestTorrent() - panicErrNil(tx.AddTorrent(testTorrent1)) - testSeeder1 := createTestPeer(createTestUserID(), testTorrent1.ID) + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + testSeeder := createTestPeer(createTestUserID(), testTorrent.ID) - panicErrNil(tx.AddSeeder(testTorrent1, testSeeder1)) - foundTorrent, found, err := tx.FindTorrent(testTorrent1.Infohash) + panicErrNil(tx.AddSeeder(testTorrent, testSeeder)) + foundTorrent, found, err := tx.FindTorrent(testTorrent.Infohash) panicErrNil(err) - foundSeeder, found := foundTorrent.Seeders[testSeeder1.ID] - if found && foundSeeder != *testSeeder1 { - t.Error("seeder not added to cache", testSeeder1) + foundSeeder, found := foundTorrent.Seeders[models.PeerMapKey(testSeeder)] + if found && foundSeeder != *testSeeder { + t.Error("seeder not added to cache", testSeeder) } - foundSeeder, found = testTorrent1.Seeders[testSeeder1.ID] - if found && foundSeeder != *testSeeder1 { - t.Error("seeder not added to local", testSeeder1) + foundSeeder, found = testTorrent.Seeders[models.PeerMapKey(testSeeder)] + if found && foundSeeder != *testSeeder { + t.Error("seeder not added to local", testSeeder) } } func TestAddLeecher(t *testing.T) { tx := createTestTx() - testTorrent1 := createTestTorrent() - panicErrNil(tx.AddTorrent(testTorrent1)) - testLeecher1 := createTestPeer(createTestUserID(), testTorrent1.ID) + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + testLeecher := createTestPeer(createTestUserID(), testTorrent.ID) - tx.AddLeecher(testTorrent1, testLeecher1) - foundTorrent, found, err := tx.FindTorrent(testTorrent1.Infohash) + panicErrNil(tx.AddLeecher(testTorrent, testLeecher)) + foundTorrent, found, err := tx.FindTorrent(testTorrent.Infohash) panicErrNil(err) - foundLeecher, found := foundTorrent.Leechers[testLeecher1.ID] - if found && foundLeecher != *testLeecher1 { - t.Error("leecher not added to cache", testLeecher1) + foundLeecher, found := foundTorrent.Leechers[models.PeerMapKey(testLeecher)] + if found && foundLeecher != *testLeecher { + t.Error("leecher not added to cache", testLeecher) } - foundLeecher, found = testTorrent1.Leechers[testLeecher1.ID] - if found && foundLeecher != *testLeecher1 { - t.Error("leecher not added to local", testLeecher1) + foundLeecher, found = testTorrent.Leechers[models.PeerMapKey(testLeecher)] + if found && foundLeecher != *testLeecher { + t.Error("leecher not added to local", testLeecher) } } func TestRemoveSeeder(t *testing.T) { tx := createTestTx() - testTorrent1 := createTestTorrent() - panicErrNil(tx.AddTorrent(testTorrent1)) - testSeeder1 := createTestPeer(createTestUserID(), testTorrent1.ID) - tx.AddSeeder(testTorrent1, testSeeder1) + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + testSeeder := createTestPeer(createTestUserID(), testTorrent.ID) + panicErrNil(tx.AddSeeder(testTorrent, testSeeder)) - panicErrNil(tx.RemoveSeeder(testTorrent1, testSeeder1)) - foundSeeder, found := testTorrent1.Seeders[testSeeder1.ID] - if found || foundSeeder == *testSeeder1 { + panicErrNil(tx.RemoveSeeder(testTorrent, testSeeder)) + foundSeeder, found := testTorrent.Seeders[models.PeerMapKey(testSeeder)] + if found || foundSeeder == *testSeeder { t.Error("seeder not removed from local", foundSeeder) } - foundTorrent, found, err := tx.FindTorrent(testTorrent1.Infohash) + foundTorrent, found, err := tx.FindTorrent(testTorrent.Infohash) panicErrNil(err) - foundSeeder, found = foundTorrent.Seeders[testSeeder1.ID] - if found || foundSeeder == *testSeeder1 { - t.Error("seeder not removed from cache", foundSeeder) + foundSeeder, found = foundTorrent.Seeders[models.PeerMapKey(testSeeder)] + if found || foundSeeder == *testSeeder { + t.Error("seeder not removed from cache", foundSeeder, *testSeeder) } } func TestRemoveLeecher(t *testing.T) { tx := createTestTx() - testTorrent1 := createTestTorrent() - panicErrNil(tx.AddTorrent(testTorrent1)) - testLeecher1 := createTestPeer(createTestUserID(), testTorrent1.ID) - tx.AddLeecher(testTorrent1, testLeecher1) + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + testLeecher := createTestPeer(createTestUserID(), testTorrent.ID) + panicErrNil(tx.AddLeecher(testTorrent, testLeecher)) - tx.RemoveLeecher(testTorrent1, testLeecher1) - foundTorrent, found, err := tx.FindTorrent(testTorrent1.Infohash) + panicErrNil(tx.RemoveLeecher(testTorrent, testLeecher)) + foundTorrent, found, err := tx.FindTorrent(testTorrent.Infohash) panicErrNil(err) - foundLeecher, found := foundTorrent.Leechers[testLeecher1.ID] - if found || foundLeecher == *testLeecher1 { - t.Error("leecher not removed from cache", foundLeecher) + foundLeecher, found := foundTorrent.Leechers[models.PeerMapKey(testLeecher)] + if found || foundLeecher == *testLeecher { + t.Error("leecher not removed from cache", foundLeecher, *testLeecher) } - foundLeecher, found = testTorrent1.Leechers[testLeecher1.ID] - if found || foundLeecher == *testLeecher1 { - t.Error("leecher not removed from local", foundLeecher) + foundLeecher, found = testTorrent.Leechers[models.PeerMapKey(testLeecher)] + if found || foundLeecher == *testLeecher { + t.Error("leecher not removed from local", foundLeecher, *testLeecher) } } func TestSetSeeder(t *testing.T) { tx := createTestTx() - testTorrent1 := createTestTorrent() - panicErrNil(tx.AddTorrent(testTorrent1)) - testSeeder1 := createTestPeer(createTestUserID(), testTorrent1.ID) - tx.AddSeeder(testTorrent1, testSeeder1) + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + testSeeder := createTestPeer(createTestUserID(), testTorrent.ID) + panicErrNil(tx.AddSeeder(testTorrent, testSeeder)) - testSeeder1.Uploaded += 100 + r := rand.New(rand.NewSource(time.Now().UnixNano())) + testSeeder.Uploaded += uint64(r.Int63()) - tx.SetSeeder(testTorrent1, testSeeder1) - foundTorrent, _, err := tx.FindTorrent(testTorrent1.Infohash) + panicErrNil(tx.SetSeeder(testTorrent, testSeeder)) + + foundTorrent, _, err := tx.FindTorrent(testTorrent.Infohash) panicErrNil(err) - foundSeeder, _ := foundTorrent.Seeders[testSeeder1.ID] - if foundSeeder != *testSeeder1 { - t.Error("seeder not updated in cache", testSeeder1) + foundSeeder, _ := foundTorrent.Seeders[models.PeerMapKey(testSeeder)] + if foundSeeder != *testSeeder { + t.Error("seeder not updated in cache", foundSeeder, *testSeeder) } - foundSeeder, _ = testTorrent1.Seeders[testSeeder1.ID] - if foundSeeder != *testSeeder1 { - t.Error("seeder not updated in local", testSeeder1) + foundSeeder, _ = testTorrent.Seeders[models.PeerMapKey(testSeeder)] + if foundSeeder != *testSeeder { + t.Error("seeder not updated in local", foundSeeder, *testSeeder) } } func TestSetLeecher(t *testing.T) { tx := createTestTx() - testTorrent1 := createTestTorrent() - panicErrNil(tx.AddTorrent(testTorrent1)) - testLeecher1 := createTestPeer(createTestUserID(), testTorrent1.ID) - tx.AddLeecher(testTorrent1, testLeecher1) + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + testLeecher := createTestPeer(createTestUserID(), testTorrent.ID) + panicErrNil(tx.AddLeecher(testTorrent, testLeecher)) - testLeecher1.Uploaded += 100 + r := rand.New(rand.NewSource(time.Now().UnixNano())) + testLeecher.Uploaded += uint64(r.Int63()) - tx.SetLeecher(testTorrent1, testLeecher1) - foundTorrent, _, err := tx.FindTorrent(testTorrent1.Infohash) + panicErrNil(tx.SetLeecher(testTorrent, testLeecher)) + foundTorrent, _, err := tx.FindTorrent(testTorrent.Infohash) panicErrNil(err) - foundLeecher, _ := foundTorrent.Leechers[testLeecher1.ID] - if foundLeecher != *testLeecher1 { - t.Error("leecher not updated in cache", testLeecher1) + foundLeecher, _ := foundTorrent.Leechers[models.PeerMapKey(testLeecher)] + if foundLeecher != *testLeecher { + t.Error("leecher not updated in cache", testLeecher) } - foundLeecher, _ = testTorrent1.Leechers[testLeecher1.ID] - if foundLeecher != *testLeecher1 { - t.Error("leecher not updated in local", testLeecher1) + foundLeecher, _ = testTorrent.Leechers[models.PeerMapKey(testLeecher)] + if foundLeecher != *testLeecher { + t.Error("leecher not updated in local", testLeecher) + } +} + +func TestIncrementSlots(t *testing.T) { + tx := createTestTx() + testUser := createTestUser() + panicErrNil(tx.AddUser(testUser)) + numSlots := testUser.Slots + + panicErrNil(tx.IncrementSlots(testUser)) + foundUser, _, err := tx.FindUser(testUser.Passkey) + panicErrNil(err) + + if foundUser.Slots != numSlots+1 { + t.Error("cached slots not incremented") + } + if testUser.Slots != numSlots+1 { + t.Error("local slots not incremented") + } +} + +func TestDecrementSlots(t *testing.T) { + tx := createTestTx() + testUser := createTestUser() + panicErrNil(tx.AddUser(testUser)) + numSlots := testUser.Slots + + panicErrNil(tx.DecrementSlots(testUser)) + foundUser, _, err := tx.FindUser(testUser.Passkey) + panicErrNil(err) + + if foundUser.Slots != numSlots-1 { + t.Error("cached slots not incremented") + } + if testUser.Slots != numSlots-1 { + t.Error("local slots not incremented") } } func TestLeecherFinished(t *testing.T) { tx := createTestTx() - testTorrent1 := createTestTorrent() - panicErrNil(tx.AddTorrent(testTorrent1)) - testLeecher1 := createTestPeer(createTestUserID(), testTorrent1.ID) - tx.AddLeecher(testTorrent1, testLeecher1) - testLeecher1.Left = 0 + testTorrent := createTestTorrent() + panicErrNil(tx.AddTorrent(testTorrent)) + testLeecher := createTestPeer(createTestUserID(), testTorrent.ID) + panicErrNil(tx.AddLeecher(testTorrent, testLeecher)) + testLeecher.Left = 0 - tx.LeecherFinished(testTorrent1, testLeecher1) - foundTorrent, _, err := tx.FindTorrent(testTorrent1.Infohash) + panicErrNil(tx.LeecherFinished(testTorrent, testLeecher)) + + foundTorrent, _, err := tx.FindTorrent(testTorrent.Infohash) panicErrNil(err) - foundSeeder, _ := foundTorrent.Seeders[testLeecher1.ID] - if foundSeeder != *testLeecher1 { - t.Error("seeder not added to cache", testLeecher1, foundSeeder) + foundSeeder, _ := foundTorrent.Seeders[models.PeerMapKey(testLeecher)] + if foundSeeder != *testLeecher { + t.Error("seeder not added to cache", foundSeeder, *testLeecher) } - foundSeeder, _ = foundTorrent.Leechers[testLeecher1.ID] - if foundSeeder == *testLeecher1 { - t.Error("leecher not removed from cache", testLeecher1) + foundSeeder, _ = foundTorrent.Leechers[models.PeerMapKey(testLeecher)] + if foundSeeder == *testLeecher { + t.Error("leecher not removed from cache", testLeecher) } - foundSeeder, _ = testTorrent1.Seeders[testLeecher1.ID] - if foundSeeder != *testLeecher1 { - t.Error("seeder not added to local", testLeecher1) + foundSeeder, _ = testTorrent.Seeders[models.PeerMapKey(testLeecher)] + if foundSeeder != *testLeecher { + t.Error("seeder not added to local", testLeecher) } - foundSeeder, _ = testTorrent1.Leechers[testLeecher1.ID] - if foundSeeder == *testLeecher1 { - t.Error("leecher not removed from local", testLeecher1) + foundSeeder, _ = testTorrent.Leechers[models.PeerMapKey(testLeecher)] + if foundSeeder == *testLeecher { + t.Error("leecher not removed from local", testLeecher) } } // Add, update, verify remove func TestUpdatePeer(t *testing.T) { tx := createTestTx() - testTorrent1 := createTestTorrent() - testSeeder1 := createTestPeer(createTestUserID(), testTorrent1.ID) - panicErrNil(tx.AddTorrent(testTorrent1)) - panicErrNil(tx.AddSeeder(testTorrent1, testSeeder1)) + testTorrent := createTestTorrent() + testSeeder := createTestPeer(createTestUserID(), testTorrent.ID) + panicErrNil(tx.AddTorrent(testTorrent)) + panicErrNil(tx.AddSeeder(testTorrent, testSeeder)) // Update a seeder, set it, then check to make sure it updated r := rand.New(rand.NewSource(time.Now().UnixNano())) - testSeeder1.Uploaded += uint64(r.Int63()) + testSeeder.Uploaded += uint64(r.Int63()) - panicErrNil(tx.SetSeeder(testTorrent1, testSeeder1)) + panicErrNil(tx.SetSeeder(testTorrent, testSeeder)) - panicErrNil(tx.RemoveSeeder(testTorrent1, testSeeder1)) - foundTorrent, _, err := tx.FindTorrent(testTorrent1.Infohash) + panicErrNil(tx.RemoveSeeder(testTorrent, testSeeder)) + foundTorrent, _, err := tx.FindTorrent(testTorrent.Infohash) panicErrNil(err) - if seeder1, exists := foundTorrent.Seeders[testSeeder1.ID]; exists { - t.Error("seeder not removed from cache", seeder1) + if seeder, exists := foundTorrent.Seeders[models.PeerMapKey(testSeeder)]; exists { + t.Error("seeder not removed from cache", seeder) } - if seeder1, exists := testTorrent1.Seeders[testSeeder1.ID]; exists { - t.Error("seeder not removed from local", seeder1) + if seeder, exists := testTorrent.Seeders[models.PeerMapKey(testSeeder)]; exists { + t.Error("seeder not removed from local", seeder) } } diff --git a/models/models.go b/models/models.go index cc1def7..1162321 100644 --- a/models/models.go +++ b/models/models.go @@ -4,6 +4,10 @@ package models +import ( + "strconv" +) + type Peer struct { ID string `json:"id"` UserID uint64 `json:"user_id"` @@ -18,6 +22,10 @@ type Peer struct { LastAnnounce int64 `json:"last_announce"` } +func PeerMapKey(peer *Peer) string { + return peer.ID + ":" + strconv.FormatUint(peer.UserID, 36) +} + type Torrent struct { ID uint64 `json:"id"` Infohash string `json:"infohash"` diff --git a/server/announce.go b/server/announce.go index 3a37e81..90f8d89 100644 --- a/server/announce.go +++ b/server/announce.go @@ -82,8 +82,8 @@ func (s Server) serveAnnounce(w http.ResponseWriter, r *http.Request) { } // Look for the user in in the pool of seeders and leechers - _, seeder := torrent.Seeders[peerID] - _, leecher := torrent.Leechers[peerID] + _, seeder := torrent.Seeders[models.PeerMapKey(peer)] + _, leecher := torrent.Leechers[models.PeerMapKey(peer)] switch { // Guarantee that no user is in both pools