Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 410 lines (344 sloc) 11.953 kB
9821eb6 @victorjonsson initial commit...
authored
1 var filesystem = require('fs'),
2 util = require('util'),
3 querystring = require('querystring');
4
5 /**
6 * Utility function for extending object properties
7 * @author tanepiper (github)
8 */
9 function extend() {
10
11 var toString = Object.prototype.toString,
12 hasOwn = Object.prototype.hasOwnProperty,
13 // [[Class]] -> type pairs
14 class2type = {};
15
16 // Populate the class2type map
17 "Boolean Number String Function Array Date RegExp Object".split(" ").forEach(function(name) {
18 class2type[ "[object " + name + "]" ] = name.toLowerCase();
19 });
20
21 function type(obj){
22 return obj == null ?
23 String( obj ) :
24 class2type[ toString.call(obj) ] || "object";
25 }
26
27 function isPlainObject( obj ) {
28 if ( !obj || type(obj) !== "object") {
29 return false;
30 }
31
32 // Not own constructor property must be Object
33 if ( obj.constructor &&
34 !hasOwn.call(obj, "constructor") &&
35 !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
36 return false;
37 }
38
39 // Own properties are enumerated firstly, so to speed up,
40 // if last one is own, then all properties are own.
41
42 var key;
43 for ( key in obj ) {}
44
45 return key === undefined || hasOwn.call( obj, key );
46 }
47
48 var options, name, src, copy, copyIsArray, clone,
49 target = arguments[0] || {},
50 i = 1,
51 length = arguments.length,
52 deep = false;
53
54 // Handle a deep copy situation
55 if ( typeof target === "boolean" ) {
56 deep = target;
57 target = arguments[1] || {};
58 // skip the boolean and the target
59 i = 2;
60 }
61
62 // Handle case when target is a string or something (possible in deep copy)
63 if ( typeof target !== "object" && type(target) !== "function") {
64 target = {};
65 }
66
67 // extend jQuery itself if only one argument is passed
68 if ( length === i ) {
69 target = this;
70 --i;
71 }
72
73 for ( ; i < length; i++ ) {
74 // Only deal with non-null/undefined values
75 if ( (options = arguments[ i ]) != null ) {
76 // Extend the base object
77 for ( name in options ) {
78 src = target[ name ];
79 copy = options[ name ];
80
81 // Prevent never-ending loop
82 if ( target === copy ) {
83 continue;
84 }
85
86 // Recurse if we're merging plain objects or arrays
87 if ( deep && copy && ( isPlainObject(copy) || (copyIsArray = type(copy) === "array") ) ) {
88 if ( copyIsArray ) {
89 copyIsArray = false;
90 clone = src && type(src) === "array" ? src : [];
91
92 } else {
93 clone = src && isPlainObject(src) ? src : {};
94 }
95
96 // Never move original objects, clone them
97 target[ name ] = extend( deep, clone, copy );
98
99 // Don't bring in undefined values
100 } else if ( copy !== undefined ) {
101 target[ name ] = copy;
102 }
103 }
104 }
105 }
106
107 // Return the modified object
108 return target;
109 }
110
111 /**
112 * @param {Object} options
113 * @return {Object}
114 */
115 function mergeWithDefaultOptions(options) {
116 var o = extend({
117 port : 80,
118 method : 'GET',
119 headers : {},
120 write : ''
121 }, options);
122 if(o.headers['Content-Length'] == undefined && typeof o.write == 'string')
123 o.headers['Content-Length'] = Buffer.byteLength(o.write, 'utf8');
124
125 if(o.url == undefined)
126 throw new Error('property url missing from options');
127 if(typeof o.url == 'object')
128 o.path = querystring.stringify(o.url);
129 else
130 o.path = o.url;
131
132 if(o.headers['User-Agent'] == undefined)
133 o['User-Agent'] = 'node dokimon 0.1';
134
135 return o;
136 }
137
138
139 /** * * * * * * * * * * * * * * * * * * * * * * * *
140 * @class {TestManager}
141 * @param {Object} config
142 * @param {Function} testFinishCallback - Optional
143 * * * * * * * * * * * * * * * * * * * * * * * * */
144 var TestManager = function(config, testFinishCallback) {
145
146 this.index = 0;
147 this.successes = [];
148 this.fails = [];
149 this.tests = [];
150
151 /**
152 * Wrapper that makes it possible to handle the
153 * message with something other then console.log
154 *
155 * @param {String} message
156 */
157 this.log = function(message) {
158 if(config.log)
159 config.log(message);
160 else
161 console.log(message);
162 };
163
164 /**
7c6efcd @victorjonsson work in progress...
authored
165 * @param {Test[]} tests
9821eb6 @victorjonsson initial commit...
authored
166 */
167 this.setTests = function(tests) {
7c6efcd @victorjonsson work in progress...
authored
168 this.tests = tests;
9821eb6 @victorjonsson initial commit...
authored
169 };
170
171 /**
172 * Runs tests starting from current position of this.index
173 */
174 this.run = function() {
175 if(config.verbose)
176 this.log('Running scripts starting from '+this.index);
177
178 for(var i=this.index; i < this.tests.length; i++) {
179 // blocking request
180 if(this.tests[i].blocking) {
181 if(config.verbose)
182 this.log('Blocking upcoming tests on index '+i);
183
184 this.index = i+1;
185 var self = this;
186 this.tests[i].execute(config.host, this, config.verbose, function() {
187 self.run();
188 });
189 break;
190 }
191 else
192 this.tests[i].execute(config.host, this, config.verbose);
193 }
194 };
195
196 /**
197 * Run a specific test in the test list
198 *
199 * @param {String} testName
200 * @param {Boolean} verbose - Optional
201 */
202 this.runTest = function(testName, verbose) {
203 for(var i=0; i < this.tests.length; i++) {
204 if(this.tests[i].name == testName) {
205 this.tests[i].execute(config.host, this, verbose);
206 return;
207 }
208 }
209
210 throw new Error('Test not found');
211 };
212
213 /**
214 * @param {Object} success
215 */
216 this.registerSuccessfullTest = function(success) {
217 this.successes.push(success);
218 this.log('* '+success.name + ' successfull');
219 _checkIfFinished(this);
220 };
221
222 /**
223 * @param {Object} fail
224 * @param {Error} err - Optional
225 */
226 this.registerFailedtest = function(fail, err) {
227 this.fails.push(fail);
228 this.log('- '+fail.name + " FAILED! \n"+fail.message);
229 if(err)
230 this.log(err);
231 _checkIfFinished(this);
232 };
233
234 /**
235 * Get the correct path to script. Throws error if not exists
236 *
237 * @param {String} name
238 * @return {String}
239 */
240 this.scriptNameToPath = function(name) {
241 var testFileName = name.substring(0, config.testdir.length) == config.testdir ? name : config.testdir+'/'+name;
242 if(!_hasDokimonExtension(testFileName))
243 testFileName += TestManager.TEST_EXTENSION;
244
245 var firsLetter = testFileName.substr(0,1);
246 if(firsLetter != '.' && firsLetter != '/')
247 testFileName = './'+testFileName;
248
249 return testFileName;
250 };
251
252 /**
253 * Get a list of all available scripts in given directory
254 *
255 * @param {String} path
256 * @return {Array}
257 */
258 this.loadScriptsInDir = function(path) {
259 var scripts = [];
260 filesystem.readdirSync(path).forEach(function(f) {
261 if(_hasDokimonExtension(f))
262 scripts.push(f.substr(0, f.length - TestManager.TEST_EXTENSION.length));
263 });
264 return scripts;
265 };
266
267 /**
268 * Will output test result to client when test i finished
269 *
270 * @access private
271 * @param {TestManager} manager
272 */
273 var _checkIfFinished = function(manager) {
274 if(manager.tests.length == (manager.successes.length + manager.fails.length)) {
275 if(manager.fails.length == 0) {
276 manager.log("\n------------------------------\nEverything is fine :)");
277 manager.log('Executed a total of '+manager.successes.length+' successfull tests');
278 }
279 else {
280 manager.log("\n------------------------------\nAll is not well :(");
281 manager.log(manager.fails.length+' test'+(manager.fails.length > 1 ? 's':'')+' failed');
282 manager.log(manager.successes.length+' test'+(manager.successes.length > 1 ? 's':'')+' was successfull');
283 }
284
285 manager.log("");
286 if(typeof testFinishCallback == 'function')
287 testFinishCallback(manager);
288 }
289 };
290
291 /**
292 * @access private
293 * @param {String} path
294 * @return {Boolean}
295 */
296 var _hasDokimonExtension = function(path) {
297 return path.substr(-1 * TestManager.TEST_EXTENSION.length) == TestManager.TEST_EXTENSION;
298 };
299 };
300
301
302 /**
303 * Extension for scripts containing tests
304 */
305 TestManager.TEST_EXTENSION = '.dokimon';
306
307
308 /** * * * * * * * * * * * * * * * * * * * * * * * *
309 * @class {Test}
310 * @param {String} testName - Name of this test
311 * @param {Object} options - requires url, optional is port:Number, method:String, write:String and headers:Object
312 * @param {Function} callback - function(response, body)
313 * @param {Boolean} blocking - Optional, whether or not this test is supposed to finish before running any other tests
314 * * * * * * * * * * * * * * * * * * * * * * * * */
315 var Test = function(testName, options, callback, blocking) {
316
317 options = mergeWithDefaultOptions(options);
318
319 this.blocking = blocking;
320 this.name = testName;
321
322 /**
7c6efcd @victorjonsson work in progress...
authored
323 * @param {TestManager} manager
9821eb6 @victorjonsson initial commit...
authored
324 * @param {Boolean} verbose - Optional
325 * @param {Function} exeFinishCallback - Optional, will be called when request is finished and test callback is called
326 */
7c6efcd @victorjonsson work in progress...
authored
327 this.execute = function(host, manager, verbose, exeFinishCallback) {
9821eb6 @victorjonsson initial commit...
authored
328
329 options.host = host;
330
331 if(verbose)
332 manager.log("About to request "+host+" for \n"+JSON.stringify(options).replace(/\,\"/g, ",\n\"")+"\n");
333
334 var req = require('http').request(options, function(res) {
335 var collectedBody = '';
336 res.setEncoding('utf8');
337 res.on('data', function (body) {
338 collectedBody += body;
339 });
340 res.on('end', function() {
341 try {
7c6efcd @victorjonsson work in progress...
authored
342 callback(res, collectedBody, verbose);
343 manager.registerSuccessfullTest({name : testName, message:''});
344 if(typeof exeFinishCallback == 'function')
345 exeFinishCallback();
346 }
347 catch(e) {
348 manager.registerFailedtest({name : testName, message:e.message}, e);
349 if(typeof exeFinishCallback == 'function')
350 exeFinishCallback();
351 }
9821eb6 @victorjonsson initial commit...
authored
352 });
353 });
354
355 req.on('error', function(e) {
7c6efcd @victorjonsson work in progress...
authored
356 manager.registerFailedtest({name : testName, message:''}, e);
9821eb6 @victorjonsson initial commit...
authored
357 });
358
359 req.write(options.write);
360 req.end();
361 };
362 };
363
364
365 /** * * * * * * * * * * * * * * * * * * * * * * * *
366 * @class {TestFormPost}
367 * @param {String} testName - Name of this test
368 * @param {Object} options - requires url, optional is port:Number, method:String, write:String and headers:Object
369 * @param {Function} callback - function(response, body)
370 * @param {Boolean} blocking - Optional, whether or not this test is supposed to finish before running any other tests
371 * * * * * * * * * * * * * * * * * * * * * * * * */
372 var TestFormPost = function(testName, options, callback, blocking) {
373
374 options = mergeWithDefaultOptions(options);
375 options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
376 if(typeof(options.write) == 'object')
377 options.write = querystring.stringify(options.write);
378
379 options.headers['Content-Length'] = Buffer.byteLength(options.write, 'utf8');
380
381 this.blocking = blocking;
382 this.name = testName;
383 };
384 util.inherits(TestFormPost, Test);
385
386
387 module.exports = {
388
389 /**
390 * @param {Object} config
391 * @return {TestManager}
392 */
393 TestManager : TestManager,
394
395 /**
396 * @return {Test}
397 */
398 Test : Test,
399
400 /**
401 * @param {Test} t
402 */
403 runTest : function(t, config, testFinishCallback) {
404 var manager = new TestManager(config, testFinishCallback);
405 manager.setTests([t]);
406 manager.run();
407 },
408
409 TEST_EXTENSION : TestManager.TEST_EXTENSION
410 };
Something went wrong with that request. Please try again.