diff --git a/makefile b/makefile index 175e0dc..0c357ba 100644 --- a/makefile +++ b/makefile @@ -16,4 +16,12 @@ test-report: --reporter markdown \ test/unit > ./test-unit.md +test-perf: + @NODE_ENV=test ./node_modules/.bin/mocha $(T) \ + --compilers coffee:coffee-script \ + --recursive \ + --reporter $(REPORTER) \ + --timeout 5000 \ + test/perf + .PHONY: test test-report diff --git a/package.json b/package.json index 0ed5b93..dc75409 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,13 @@ , "moment": "2.1.0" }, "devDependencies": { - "should": "1.2.2" - , "sinon": "v1.7.3" + "sinon": "v1.7.3" , "mocha": "1.12.1" , "async": "0.2.5" , "coffee-script": "1.5.0" + , "memwatch": "v0.2.2" + , "winston": "v0.7.3" + , "chalk": "v0.4.0" }, "keywords": [ "nodeJs" diff --git a/test/bootstrap-perf.coffee b/test/bootstrap-perf.coffee new file mode 100644 index 0000000..f52e420 --- /dev/null +++ b/test/bootstrap-perf.coffee @@ -0,0 +1,4 @@ +mongoose = require 'mongoose' + +mongoose.connect "mongodb://127.0.0.1:27017/mongoose-rattle-test-perf", {}, (err) -> + throw err if err diff --git a/test/perf-data.coffee b/test/perf-data.coffee new file mode 100644 index 0000000..14bc944 --- /dev/null +++ b/test/perf-data.coffee @@ -0,0 +1,86 @@ +require './bootstrap-perf' + +mongoose = require 'mongoose' +async = require 'async' +winston = require 'winston' + +winston.remove winston.transports.Console +winston.add winston.transports.Console, colorize: true + +Thingy = require './models/thingy' + +ObjectId = mongoose.Types.ObjectId; + +numThingies = 5 +numLikes = 20000 +numCommentLikes = 20 +blockNumComments = 5000 +numBlocks = 4 + +i = 0 +likes = [] +while i < numCommentLikes + likes.push new ObjectId() + i++ + +dummyComment = + message: 'duuuuuuuuuuuummmmmmmmmmmmmmmmyyyyyyyyyyyyy meeeeeeeeeeeeessssssssssssaaaaaaaaaaaaaageeeeee' + creator: new ObjectId() + likes: likes + likesCount: type: Number, default: 0 + dateCreation: type: Date + dateUpdate: type: Date + +i = 0 +comments = [] +while i < blockNumComments + comments.push dummyComment + i++ + +createNewThingy = (callback) -> + async.waterfall [ + createThingy = (next) -> + winston.info('Create a new thingy'); + new Thingy( + creator: new ObjectId() + ).save next + addComments = (createdThingy, numAffected, next) -> + winston.info('Add comments with likes'); + + i = 0 + async.whilst (-> + i < numBlocks + ), ((callback) -> + winston.info('Add block ' + (i + 1) + ' of ' + blockNumComments + ' comments'); + Thingy.update { _id: createdThingy._id }, { $pushAll: { comments: comments } }, (err) -> + i++ + callback() + ), (err) -> + winston.info('Add likes'); + i = 0 + likes = [] + while i < numLikes + likes.push new ObjectId() + i++ + createdThingy.likes = likes + createdThingy.save next + + ], (err) -> + return callback(err) if err + winston.info('Thingy saved'); + callback() + + +winston.info('Clear thingy collection'); +Thingy.remove (err) -> + count = 0 + async.whilst (-> + count < numThingies + ), ((next) -> + createNewThingy (err) -> + return next(err) if err + count++ + next() + ), (err) -> + winston.error err if err + process.exit(1); \ No newline at end of file diff --git a/test/perf-stats.coffee b/test/perf-stats.coffee new file mode 100644 index 0000000..8f38e9d --- /dev/null +++ b/test/perf-stats.coffee @@ -0,0 +1,78 @@ +require './bootstrap-perf' + +mongoose = require 'mongoose' +async = require 'async' +memwatch = require 'memwatch' +chalk = require 'chalk' + +log = (level, msg) -> + console.log(Array(level).join(' ') + msg) +title = (msg) -> + log(1, chalk.bold.magenta.underline(msg)) +description = (msg) -> + log(1, chalk.bold.italic(msg)) +target = (msg) -> + log(2, chalk.green(msg)) +info = (msg) -> + log(4, chalk.blue(msg)) + +Thingy = require './models/thingy' + +numOccurence = 3 + +# hd = new memwatch.HeapDiff() +# diff = hd.end() +# log.info('memory diff after the operation (in bytes): ' + diff.change.size_bytes) + +task = () -> + msg = arguments[0] + obj = arguments[1] + fctName = arguments[2] + args = Array::slice.call(arguments, 3, arguments.length - 1) + callback = arguments[arguments.length - 1] + + log(3, chalk.yellow(msg)) + count = 1 + totalTime = 0 + async.whilst (-> + count <= numOccurence + ), ((next) -> + start = Date.now() + + cb = (err) -> + return next(err) if err + end = Date.now() + time = end - start + totalTime += time + info("[iteration #{count}] time for the operation (in ms): #{time}") + count++ + next() + + fctArgs = args.slice(0) + fctArgs.push(cb) + obj[fctName].apply obj, fctArgs + return + ), (err) -> + return callback(err) if err + avg = Math.ceil(totalTime / numOccurence) + info("=> average time: #{avg}") + callback() + +title("Benchmarking rattle plugin (number of occurence: #{numOccurence})") +description('In collections: 2 documents with 20000 likes, 20000 comments with 20 likes each') + +# describe 'Thingy', -> +# describe 'getList', -> +# analyse('retrieve 2 documents with no comments', Thingy, 'getList', 2, 0, next) +# analyse('retrieve 3 documents with the last 5 comments', Thingy, 'getList', 3, 5, next) + +async.series [ + getList = (done) -> + target('#getList') + async.series [ + (next) -> + task('retrieve two documents with no comments', Thingy, 'getList', 2, 0, next) + ], done + +], (err) -> + process.exit(1); diff --git a/test/unit/index.coffee b/test/unit/index.coffee index b96060f..ad17cf4 100644 --- a/test/unit/index.coffee +++ b/test/unit/index.coffee @@ -4,7 +4,6 @@ async = require 'async' sinon = require 'sinon' assert = require 'assert' moment = require 'moment' -should = require 'should' mongoose = require 'mongoose' Thingy = require '../models/thingy' @@ -23,14 +22,14 @@ describe "MongooseRattlePlugin", -> describe "document.save(callback)", -> it "update dateCreation and dateUpdate when inserting", (done) -> clock = sinon.useFakeTimers() - new Thingy(creator: objectCreatorUserId, owner: objectCreatorUserId).save (err, thingySaved) -> + new Thingy(creator: objectCreatorUserId).save (err, thingySaved) -> assert.deepEqual(new Date(), thingySaved.dateCreation) assert.deepEqual(new Date(), thingySaved.dateUpdate) clock.restore() done() it "only update dateUpdate when updating", (done) -> clock = sinon.useFakeTimers(new Date(2011, 0, 1, 1, 1, 36).getTime()) - new Thingy(creator: objectCreatorUserId, owner: objectCreatorUserId).save (err, thingySaved) -> + new Thingy(creator: objectCreatorUserId).save (err, thingySaved) -> clock.restore() clock = sinon.useFakeTimers(new Date(2012, 0, 1, 1, 1, 36).getTime()) thingySaved.save (err, thingySaved) -> @@ -41,7 +40,7 @@ describe "MongooseRattlePlugin", -> describe "Plugin methods", -> beforeEach (done) -> - new Thingy(creator: objectCreatorUserId, owner: objectCreatorUserId).save (err, thingySaved) -> + new Thingy(creator: objectCreatorUserId).save (err, thingySaved) -> thingy = thingySaved done() @@ -75,10 +74,10 @@ describe "MongooseRattlePlugin", -> describe "document.addComment(userId, message, callback)", -> it "append a new comment and return comment id", (done) -> commentId = thingy.addComment commentorUserId, 'dummy message', (err) -> - should.not.exists(err) - should.exists(commentId) + assert !err + assert commentId Thingy.findById thingy._id, (err, updatedThingy) -> - should.exists(updatedThingy) + assert updatedThingy assert.equal(1, updatedThingy.comments.length) done() it "update dateCreation and dateUpdated", (done) -> @@ -90,7 +89,7 @@ describe "MongooseRattlePlugin", -> done() it "fails if message length is out of min and max", (done) -> thingy.addComment commentorUserId, '', (err) -> - should.exists(err) + assert err done() describe "document.editComment(userId, commentId, message, callback)", -> @@ -104,20 +103,20 @@ describe "MongooseRattlePlugin", -> it "fails if message length is out of min and max", (done) -> thingy.editComment commentorUserId, commentId, '', (err) -> - should.exists(err) + assert err done() describe 'when user is not the creator', -> it "always fails", (done) -> thingy.editComment 'n0t3x1t1n9', commentId, updatedMessage, (err) -> - should.exists(err) + assert err done() describe 'when user is the creator', -> checkEditCommentWhenOwner = (commentorUserId, commentId, updatedMessage, done) -> thingy.editComment commentorUserId, commentId, updatedMessage, (err) -> - should.not.exists(err) - should.exists(commentId) + assert !err + assert commentId Thingy.findById thingy._id, (err, updatedThingy) -> - should.exists(updatedThingy) + assert updatedThingy assert.equal(1, updatedThingy.comments.length) assert.equal(updatedMessage, updatedThingy.comments[0].message) done() @@ -150,29 +149,29 @@ describe "MongooseRattlePlugin", -> it "fails if comment doesn't exist", (done) -> thingy.removeComment commentorUserId, 'n0t3x1t1n9', (err, updatedThingy) -> - should.exists(err) + assert err done() describe 'when user is not the creator', -> it "it's not removing the comment", (done) -> thingy.removeComment 'n0t3x1t1n9', commentIds['level 1'], (err, updatedThingy) -> - should.exists(updatedThingy) - should.exists(updatedThingy.getComment(commentIds['level 1'])) + assert updatedThingy + assert updatedThingy.getComment(commentIds['level 1']) done() describe 'when user is the creator', -> it "can remove comment", (done) -> thingy.removeComment commentorUserId, commentIds['level 1'], (err, updatedThingy) -> - should.exists(updatedThingy) - should.not.exists(updatedThingy.getComment(commentIds['level 1'])) + assert updatedThingy + assert !updatedThingy.getComment(commentIds['level 1']) done() it "remove comment when userId param is a string", (done) -> thingy.removeComment String(commentorUserId), commentIds['level 1'], (err, updatedThingy) -> - should.exists(updatedThingy) - should.not.exists(updatedThingy.getComment(commentIds['level 1'])) + assert updatedThingy + assert !updatedThingy.getComment(commentIds['level 1']) done() it "remove comment when commentId is a string", (done) -> thingy.removeComment commentorUserId, String(commentIds['level 1']), (err, updatedThingy) -> - should.exists(updatedThingy) - should.not.exists(updatedThingy.getComment(commentIds['level 1'])) + assert updatedThingy + assert !updatedThingy.getComment(commentIds['level 1']) done() describe "document.addLike(userId, callback)", -> @@ -253,7 +252,7 @@ describe "MongooseRattlePlugin", -> it "fails if comment doesn't exist", (done) -> thingy.addLikeToComment commentorUserId, 'n0t3x1t1n9', (err, updatedThingy) -> - should.exists(err) + assert err done() it "add one user like if user doesn't already liked and comment exists", (done) -> thingy.addLikeToComment commentorUserId, commentId, (err, updatedThingy) -> @@ -285,7 +284,7 @@ describe "MongooseRattlePlugin", -> it "fails if comment doesn't exist", (done) -> thingy.removeLikeFromComment commentorUserId, 'n0t3x1t1n9', (err, updatedThingy) -> - should.exists(err) + assert err done() it "not affect current likes list if user didn'nt already liked", (done) -> thingy.removeLikeFromComment new ObjectId(), commentId, (err, updatedThingy) -> @@ -335,7 +334,7 @@ describe "MongooseRattlePlugin", -> it "get list of the number of 'num' last rattles and return likesCount instead of likes array", (done) -> Thingy.find {}, (err, rattles) -> Thingy.getList 1, 0, (err, rattles) -> - should.not.exists(err) + assert !err assert.equal rattles.length, 1 assert.deepEqual rattles[0].creator, creator2Id assert !rattles[0].likes @@ -343,23 +342,23 @@ describe "MongooseRattlePlugin", -> done() it "get all rattles if 'num' is greater than the number of rattles", (done) -> Thingy.getList 3, 0, (err, rattles) -> - should.not.exists(err) + assert !err assert.equal rattles.length, 2 done() it "each rattle get the maximum of 'maxLastComments' last comments", (done) -> Thingy.getList 1, 1, (err, rattles) -> - should.not.exists(err) + assert !err assert.equal rattles.length, 1 assert.deepEqual rattles[0].creator, creator2Id - should.exists(rattles[0].comments) + assert rattles[0].comments assert.equal rattles[0].comments.length, 1 assert.equal rattles[0].comments[0].message, '22' done() it "each all comments when 'maxLastComments' is greater than number of comments", (done) -> Thingy.getList 1, 3, (err, rattles) -> - should.not.exists(err) + assert !err assert.equal rattles.length, 1 - should.exists(rattles[0].comments) + assert rattles[0].comments assert.equal rattles[0].comments.length, 2 done() describe "(num, maxNumLastPostComments, options, callback)", -> @@ -368,7 +367,7 @@ describe "MongooseRattlePlugin", -> # retrieve last rattle Thingy.getList 1, 0, (err, rattles) -> Thingy.getList 1, 0, fromCreationDate: rattles[0].dateCreation, (err, rattles) -> - should.not.exists(err) + assert !err assert.equal rattles.length, 1 assert.deepEqual rattles[0].creator, creator1Id done() @@ -376,17 +375,17 @@ describe "MongooseRattlePlugin", -> # retrieve last rattle Thingy.getList 1, 0, (err, rattles) -> Thingy.getList 2, 0, fromCreationDate: rattles[0].dateCreation, (err, rattles) -> - should.not.exists(err) + assert !err assert.equal rattles.length, 1 done() it "each rattle get the maximum of 'maxLastComments' last comments", (done) -> # retrieve last rattle Thingy.getList 1, 0, (err, rattles) -> Thingy.getList 1, 1, fromCreationDate: rattles[0].dateCreation, (err, rattles) -> - should.not.exists(err) + assert !err assert.equal rattles.length, 1 assert.deepEqual rattles[0].creator, creator1Id - should.exists(rattles[0].comments) + assert rattles[0].comments assert.equal rattles[0].comments.length, 1 assert.equal rattles[0].comments[0].message, '12' done() @@ -395,9 +394,9 @@ describe "MongooseRattlePlugin", -> new User({_id: creator2Id, name: 'Dummy Name'}).save (err) -> # retrieve last rattle Thingy.getList 1, 0, {populate: 'creator'}, (err, rattles) -> - should.not.exists(err) + assert !err assert.equal rattles.length, 1 - should.exists(rattles[0].creator.name) + assert rattles[0].creator.name assert.equal rattles[0].creator.name, 'Dummy Name' done() @@ -431,31 +430,31 @@ describe "MongooseRattlePlugin", -> it "get last 'num' of comments for 'rattleId' when offsetFromEnd is 0", (done) -> Thingy.getListOfCommentsById rattleId, 1, 0, (err, comments) -> - should.not.exists(err) + assert !err assert.equal comments.length, 1 assert.equal comments[0].message, '13' done() it "get last num of comments from the offsetFromEnd", (done) -> Thingy.getListOfCommentsById rattleId, 1, 1, (err, comments) -> - should.not.exists(err) + assert !err assert.equal comments.length, 1 assert.equal comments[0].message, '12' done() it "get no comments when offsetFromEnd is equal to the number of comments", (done) -> Thingy.getListOfCommentsById rattleId, 1, 3, (err, comments) -> - should.not.exists(err) + assert !err assert.equal comments.length, 0 done() it "limit comments when offsetFromEnd + num is greater that the number of comments", (done) -> Thingy.getListOfCommentsById rattleId, 3, 1, (err, comments) -> - should.not.exists(err) + assert !err assert.equal comments[0].message, '11' assert.equal comments[1].message, '12' assert.equal comments.length, 2 done() it "keep comments order", (done) -> Thingy.getListOfCommentsById rattleId, 3, 0, (err, comments) -> - should.not.exists(err) + assert !err assert.equal comments[0].message, '11' assert.equal comments[1].message, '12' assert.equal comments[2].message, '13'