~divmod-dev/divmod.org/trunk

« back to all changes in this revision

Viewing changes to Nevow/nevow/js/Divmod/UnitTest.js

  • Committer: Jean-Paul Calderone
  • Date: 2014-06-29 20:33:04 UTC
  • mfrom: (2749.1.1 remove-epsilon-1325289)
  • Revision ID: exarkun@twistedmatrix.com-20140629203304-gdkmbwl1suei4m97
mergeĀ lp:~exarkun/divmod.org/remove-epsilon-1325289

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
// -*- test-case-name: nevow.test.test_javascript -*-
2
 
 
3
 
/**
4
 
 * JavaScript unit testing framework, modeled on xUnit.
5
 
 */
6
 
 
7
 
 
8
 
// import Divmod
9
 
// import Divmod.Inspect
10
 
// import Divmod.Runtime
11
 
 
12
 
 
13
 
/**
14
 
 * Return a suite which contains every test defined in C{testClass}. Assumes
15
 
 * that if a method name starts with C{test_}, then it is a test.
16
 
 */
17
 
Divmod.UnitTest.loadFromClass = function loadFromClass(testClass) {
18
 
    var prefix = 'test_';
19
 
    var suite = Divmod.UnitTest.TestSuite();
20
 
    var methods = Divmod.Inspect.methods(testClass);
21
 
    for (var i = 0; i < methods.length; ++i) {
22
 
        var name = methods[i];
23
 
        // XXX - abstract startsWith
24
 
        if (name.substr(0, prefix.length) == prefix) {
25
 
            suite.addTest(testClass(name));
26
 
        }
27
 
    }
28
 
    return suite;
29
 
};
30
 
 
31
 
 
32
 
/**
33
 
 * Return C{true} is given value is a subclass of L{Divmod.UnitTest.TestCase},
34
 
 * C{false} otherwise.
35
 
 */
36
 
Divmod.UnitTest.isTestCaseClass = function isTestCaseClass(klass) {
37
 
    if (klass.subclassOf === undefined) {
38
 
        return false;
39
 
    }
40
 
    return klass.subclassOf(Divmod.UnitTest.TestCase);
41
 
};
42
 
 
43
 
 
44
 
/**
45
 
 * Return a suite which contains every test defined in C{testModule}.
46
 
 */
47
 
Divmod.UnitTest.loadFromModule = function loadFromModule(testModule) {
48
 
    var suite = Divmod.UnitTest.TestSuite();
49
 
    for (var name in testModule) {
50
 
        if (Divmod.UnitTest.isTestCaseClass(testModule[name])) {
51
 
            suite.addTest(Divmod.UnitTest.loadFromClass(testModule[name]));
52
 
        }
53
 
    }
54
 
    return suite;
55
 
};
56
 
 
57
 
 
58
 
 
59
 
/**
60
 
 * Raised to indicate that a test has failed.
61
 
 */
62
 
Divmod.UnitTest.AssertionError = Divmod.Error.subclass('Divmod.UnitTest.AssertionError');
63
 
Divmod.UnitTest.AssertionError.methods(
64
 
    function toString(self) {
65
 
        return 'AssertionError: ' + self.message;
66
 
    });
67
 
 
68
 
 
69
 
/**
70
 
 * Represents the results of a run of unit tests.
71
 
 *
72
 
 * @type testsRun: integer
73
 
 * @ivar testsRun: The number of tests that have been run using this as the
74
 
 *                 result.
75
 
 *
76
 
 * @type failures: Array of [L{TestCase}, L{Divmod.Error}] pairs
77
 
 * @ivar failures: The assertion failures that have occurred in this test run,
78
 
 *                 paired with the tests that generated them.
79
 
 *
80
 
 * @type successes: Array of L{TestCase}
81
 
 * @ivar successes: A list of tests that succeeded.
82
 
 *
83
 
 * @type errors: Array of [L{TestCase}, L{Divmod.Error}] pairs
84
 
 * @ivar errors: The errors that were raised by tests in this test run, paired
85
 
 *               with the tests that generated them.
86
 
 */
87
 
Divmod.UnitTest.TestResult = Divmod.Class.subclass('Divmod.UnitTest.TestResult');
88
 
Divmod.UnitTest.TestResult.methods(
89
 
    function __init__(self) {
90
 
        self.testsRun = 0;
91
 
        self.failures = [];
92
 
        self.successes = [];
93
 
        self.errors = [];
94
 
    },
95
 
 
96
 
 
97
 
    /**
98
 
     * Called by C{TestCase.run} at the start of the test.
99
 
     *
100
 
     * @param test: The test that just started.
101
 
     * @type test: L{Divmod.UnitTest.TestCase}
102
 
     */
103
 
    function startTest(self, test) {
104
 
        self.testsRun++;
105
 
    },
106
 
 
107
 
 
108
 
    /**
109
 
     * Called by C{TestCase.run} at the end of the test run.
110
 
     *
111
 
     * @param test: The test that just finished.
112
 
     * @type test: L{Divmod.UnitTest.TestCase}
113
 
     */
114
 
    function stopTest(self, test) {
115
 
    },
116
 
 
117
 
 
118
 
    /**
119
 
     * Report an error that occurred while running the given test.
120
 
     *
121
 
     * @param test: The test that had an error.
122
 
     * @type test: L{Divmod.UnitTest.TestCase}
123
 
     *
124
 
     * @param error: The error that occurred.
125
 
     * @type error: Generally a L{Divmod.Error} instance.
126
 
     */
127
 
    function addError(self, test, error) {
128
 
        self.errors.push([test, error]);
129
 
    },
130
 
 
131
 
 
132
 
    /**
133
 
     * Report a failed assertion that occurred while running the given test.
134
 
     *
135
 
     * @param test: The test with the failed assertion.
136
 
     * @type test: L{Divmod.UnitTest.TestCase}
137
 
     *
138
 
     * @param failure: The failure that occurred.
139
 
     * @type failure: A L{Divmod.UnitTest.AssertionError} instance.
140
 
     */
141
 
    function addFailure(self, test, failure) {
142
 
        self.failures.push([test, failure]);
143
 
    },
144
 
 
145
 
 
146
 
    /**
147
 
     * Report that the given test succeeded.
148
 
     *
149
 
     * @param test: The test that succeeded.
150
 
     * @type test: L{Divmod.UnitTest.TestCase}
151
 
     */
152
 
    function addSuccess(self, test) {
153
 
        self.successes.push(test);
154
 
    },
155
 
 
156
 
 
157
 
    /**
158
 
     * Return a triple of (tests run, number of failures, number of errors)
159
 
     */
160
 
    function getSummary(self) {
161
 
        return [self.testsRun, self.failures.length, self.errors.length];
162
 
    },
163
 
 
164
 
 
165
 
    /**
166
 
     * Return C{true} if there have been no failures or errors. Return C{false}
167
 
     * if there have been.
168
 
     */
169
 
    function wasSuccessful(self) {
170
 
        return self.failures.length == 0 && self.errors.length == 0;
171
 
    });
172
 
 
173
 
 
174
 
 
175
 
Divmod.UnitTest.SubunitTestClient = Divmod.UnitTest.TestResult.subclass('Divmod.UnitTest.SubunitTestClient');
176
 
Divmod.UnitTest.SubunitTestClient.methods(
177
 
    function _write(self, string) {
178
 
        print(string);
179
 
    },
180
 
 
181
 
    function _sendException(self, error) {
182
 
        var f = Divmod.Defer.Failure(error);
183
 
        self._write(f.toPrettyText(f.filteredParseStack()));
184
 
    },
185
 
 
186
 
    function addError(self, test, error) {
187
 
        self._write("error: " + test.id() + " [");
188
 
        self._sendException(error);
189
 
        self._write(']');
190
 
    },
191
 
 
192
 
    function addFailure(self, test, error) {
193
 
        self._write("failure: " + test.id() + " [");
194
 
        self._sendException(error);
195
 
        self._write(']');
196
 
    },
197
 
 
198
 
    function addSuccess(self, test) {
199
 
        self._write('successful: ' + test.id());
200
 
    },
201
 
 
202
 
    function startTest(self, test) {
203
 
        self._write('test: ' + test.id());
204
 
    });
205
 
 
206
 
 
207
 
 
208
 
/**
209
 
 * Represents a collection of tests. Implements the Composite pattern.
210
 
 */
211
 
Divmod.UnitTest.TestSuite = Divmod.Class.subclass('Divmod.UnitTest.TestSuite');
212
 
Divmod.UnitTest.TestSuite.methods(
213
 
    function __init__(self, /* optional */ tests) {
214
 
        self.tests = [];
215
 
        if (tests != undefined) {
216
 
            self.addTests(tests);
217
 
        }
218
 
    },
219
 
 
220
 
 
221
 
    /**
222
 
     * Add the given test to the suite.
223
 
     *
224
 
     * @param test: The test to add.
225
 
     * @type test: L{Divmod.UnitTest.TestCase} or L{Divmod.UnitTest.TestSuite}
226
 
     */
227
 
    function addTest(self, test) {
228
 
        self.tests.push(test);
229
 
    },
230
 
 
231
 
 
232
 
    /**
233
 
     * Add the given tests to the suite.
234
 
     *
235
 
     * @param tests: An array of tests to add.
236
 
     * @type tests: [L{Divmod.UnitTest.TestCase} or L{Divmod.UnitTest.TestSuite}]
237
 
     */
238
 
    function addTests(self, tests) {
239
 
        for (var i = 0; i < tests.length; ++i) {
240
 
            self.addTest(tests[i]);
241
 
        }
242
 
    },
243
 
 
244
 
 
245
 
    /**
246
 
     * Return the number of actual tests contained in this suite.
247
 
     */
248
 
    function countTestCases(self) {
249
 
        var total = 0;
250
 
        self.visit(function (test) { total += test.countTestCases(); });
251
 
        return total;
252
 
    },
253
 
 
254
 
 
255
 
    /**
256
 
     * Visit each test case in this suite with the given visitor function.
257
 
     */
258
 
    function visit(self, visitor) {
259
 
        for (var i = 0; i < self.tests.length; ++i) {
260
 
            self.tests[i].visit(visitor);
261
 
        }
262
 
    },
263
 
 
264
 
 
265
 
    /**
266
 
     * Run all of the tests in the suite.
267
 
     */
268
 
    function run(self, result) {
269
 
        self.visit(function (test) { test.run(result); });
270
 
    });
271
 
 
272
 
 
273
 
 
274
 
/**
275
 
 * I represent a single unit test.
276
 
 */
277
 
Divmod.UnitTest.TestCase = Divmod.Class.subclass('Divmod.UnitTest.TestCase');
278
 
Divmod.UnitTest.TestCase.methods(
279
 
    /**
280
 
     * Construct a test.
281
 
     *
282
 
     * @type methodName: string
283
 
     * @param methodName: The name of a method on this object that contains
284
 
     * the unit test.
285
 
     */
286
 
    function __init__(self, methodName) {
287
 
        self._methodName = methodName;
288
 
    },
289
 
 
290
 
 
291
 
    /**
292
 
     * Return a string which identifies this test.
293
 
     */
294
 
    function id(self) {
295
 
        return self.__class__.__name__ + '.' + self._methodName;
296
 
    },
297
 
 
298
 
 
299
 
    /**
300
 
     * Count the number of test cases in this test. Always 1, because an
301
 
     * instance represents a single test.
302
 
     */
303
 
    function countTestCases(self) {
304
 
        return 1;
305
 
    },
306
 
 
307
 
 
308
 
    /**
309
 
     * Visit this test case.
310
 
     *
311
 
     * @param visitor: A callable which takes one argument (a test case).
312
 
     */
313
 
    function visit(self, visitor) {
314
 
        visitor(self);
315
 
    },
316
 
 
317
 
 
318
 
    /**
319
 
     * Fail the test. Equivalent to an invalid assertion.
320
 
     *
321
 
     * @type reason: text
322
 
     * @param reason: Why the test is being failed.
323
 
     * @throw: Divmod.UnitTest.AssertionError
324
 
     */
325
 
    function fail(self, reason) {
326
 
        throw Divmod.UnitTest.AssertionError(reason);
327
 
    },
328
 
 
329
 
 
330
 
    /**
331
 
     * Assert that the given expression evalutates to true.
332
 
     *
333
 
     * @type expression: boolean
334
 
     * @param expression: The thing we are asserting.
335
 
     *
336
 
     * @type message: text
337
 
     * @param message: An optional parameter, explaining what the assertion
338
 
     * means.
339
 
     */
340
 
    function assert(self, expression, /* optional */ message) {
341
 
        if (!expression) {
342
 
            self.fail(message);
343
 
        }
344
 
    },
345
 
 
346
 
 
347
 
    /**
348
 
     * Compare C{a} and C{b} using the provided predicate.
349
 
     *
350
 
     * @type predicate: A callable that accepts two parameters.
351
 
     * @param predicate: Returns either C{true} or C{false}.
352
 
     *
353
 
     * @type description: text
354
 
     * @param description: Describes the inverse of the comparison. This is
355
 
     *                     used in the L{AssertionError} if the comparison
356
 
     *                     fails.
357
 
     *
358
 
     * @type a: any
359
 
     * @param a: The thing to be compared with C{b}. Passed as the first
360
 
     *           parameter to C{predicate}.
361
 
     *
362
 
     * @type b: any
363
 
     * @param b: The thing to be compared with C{a}. Passed as the second
364
 
     *           parameter to C{predicate}.
365
 
     *
366
 
     * @type message: text
367
 
     * @param message: An optional message to be included in the raised
368
 
     *                 L{AssertionError}.
369
 
     *
370
 
     * @raises L{Divmod.UnitTest.AssertionError} if C{predicate} returns
371
 
     * C{false}.
372
 
     */
373
 
    function compare(self, predicate, description, a, b,
374
 
                     /* optional */ message) {
375
 
        var repr = Divmod.UnitTest.repr;
376
 
        if (!predicate(a, b)) {
377
 
            msg = repr(a) + " " + description + " " + repr(b);
378
 
            if (message != null) {
379
 
                msg += ': ' + message;
380
 
            }
381
 
            self.fail(msg);
382
 
        }
383
 
    },
384
 
 
385
 
 
386
 
    /**
387
 
     * Assert that C{a} and C{b} are equal. Recurses into arrays and dicts.
388
 
     */
389
 
    function assertArraysEqual(self, a, b, /* optional */ message) {
390
 
        self.compare(Divmod.arraysEqual, '!=', a, b, message);
391
 
    },
392
 
 
393
 
 
394
 
    /**
395
 
     * Assert that C{a} and C{b} are identical.
396
 
     */
397
 
    function assertIdentical(self, a, b, /* optional */ message) {
398
 
        self.compare(function (x, y) { return x === y; },
399
 
                     '!==', a, b, message);
400
 
    },
401
 
 
402
 
 
403
 
    /**
404
 
     * Assert that C{callable} throws C{expectedError}
405
 
     *
406
 
     * @param expectedError: The error type (class or prototype) which is
407
 
     * expected to be thrown.
408
 
     *
409
 
     * @param callable: A callable which is expected to throw C{expectedError}.
410
 
     *
411
 
     * @param ...: Optional positional arguments passed to C{callable}.
412
 
     *
413
 
     * @throw AssertionError: Thrown if the callable doesn't throw
414
 
     * C{expectedError}. This could be because it threw a different error or
415
 
     * because it didn't throw any errors.
416
 
     *
417
 
     * @return: The exception that was raised by callable.
418
 
     */
419
 
    function assertThrows(self, expectedError, callable /*... */) {
420
 
        var threw = null;
421
 
        var args = Array.prototype.slice.call(arguments, 3);
422
 
        try {
423
 
            callable.apply(null, args);
424
 
        } catch (e) {
425
 
            threw = e;
426
 
            self.assert(e instanceof expectedError,
427
 
                        "Wrong error type thrown: " + e);
428
 
        }
429
 
        self.assert(threw != null, "Callable threw no error");
430
 
        return threw;
431
 
    },
432
 
 
433
 
 
434
 
    /**
435
 
     * Override me to provide code to set up a unit test. This method is called
436
 
     * before the test method.
437
 
     *
438
 
     * L{setUp} is most useful when a subclass contains many test methods which
439
 
     * require a common base configuration. L{tearDown} is the complement of
440
 
     * L{setUp}.
441
 
     */
442
 
    function setUp(self) {
443
 
    },
444
 
 
445
 
 
446
 
    /**
447
 
     * Override me to provide code to clean up a unit test. This method is called
448
 
     * after the test method.
449
 
     *
450
 
     * L{tearDown} is at its most useful when used to clean up resources that are
451
 
     * initialized/modified by L{setUp} or by the test method.
452
 
     */
453
 
    function tearDown(self) {
454
 
    },
455
 
 
456
 
 
457
 
    /**
458
 
     * Actually run this test.
459
 
     */
460
 
    function run(self, result) {
461
 
        var success = true;
462
 
        result.startTest(self);
463
 
 
464
 
        // XXX: This probably isn't the best place to put this, but it's the
465
 
        // only place for the time being; see #2806 for the proper way to deal
466
 
        // with this.
467
 
        Divmod.Runtime.initRuntime();
468
 
 
469
 
        try {
470
 
            self.setUp();
471
 
        } catch (e) {
472
 
            result.addError(self, e);
473
 
            return result;
474
 
        }
475
 
        try {
476
 
            self[self._methodName]();
477
 
        } catch (e) {
478
 
            if (e instanceof Divmod.UnitTest.AssertionError) {
479
 
                result.addFailure(self, e);
480
 
            } else {
481
 
                result.addError(self, e);
482
 
            }
483
 
            success = false;
484
 
        }
485
 
        try {
486
 
            self.tearDown();
487
 
        } catch (e) {
488
 
            result.addError(self, e);
489
 
            success = false;
490
 
        }
491
 
        if (success) {
492
 
            result.addSuccess(self);
493
 
        }
494
 
        result.stopTest(self);
495
 
    });
496
 
 
497
 
 
498
 
 
499
 
/**
500
 
 * Return a nicely formatted summary from the given L{TestResult}.
501
 
 */
502
 
Divmod.UnitTest.formatSummary = function formatSummary(result) {
503
 
    var summary;
504
 
    if (result.wasSuccessful()) {
505
 
        summary = "PASSED "
506
 
    } else {
507
 
        summary = "FAILED "
508
 
    }
509
 
    summary += "(tests=" + result.testsRun;
510
 
    if (result.errors.length > 0) {
511
 
        summary += ", errors=" + result.errors.length
512
 
    }
513
 
    if (result.failures.length > 0) {
514
 
        summary += ", failures=" + result.failures.length;
515
 
    }
516
 
    summary += ')';
517
 
    return summary;
518
 
};
519
 
 
520
 
 
521
 
 
522
 
/**
523
 
 * Return a formatted string containing all the errors and failures in a result
524
 
 *
525
 
 * @param result: A test result.
526
 
 * @type result: L{Divmod.UnitTest.TestResult}
527
 
 */
528
 
Divmod.UnitTest.formatErrors = function formatErrors(result) {
529
 
    var format = '';
530
 
    for (var i = 0; i < result.errors.length; ++i) {
531
 
        format += Divmod.UnitTest.formatError('ERROR',
532
 
                                              result.errors[i][0],
533
 
                                              result.errors[i][1]);
534
 
    }
535
 
    for (var i = 0; i < result.failures.length; ++i) {
536
 
        format += Divmod.UnitTest.formatError('FAILURE',
537
 
                                              result.failures[i][0],
538
 
                                              result.failures[i][1]);
539
 
    }
540
 
    return format;
541
 
};
542
 
 
543
 
 
544
 
 
545
 
/**
546
 
 * Return a formatting string showing the failure/error that occurred in a test.
547
 
 *
548
 
 * @param test: A test which had a failure or error.
549
 
 * @type test: L{Divmod.UnitTest.TestCase}
550
 
 *
551
 
 * @param error: An error or failure which occurred in the test.
552
 
 * @type error: L{Divmod.Error}
553
 
 */
554
 
Divmod.UnitTest.formatError = function formatError(kind, test, error) {
555
 
    var f = Divmod.Defer.Failure(error);
556
 
    var ret = '[' + kind + '] ' + test.id() + ': ' + error.message + '\n';
557
 
    ret += f.toPrettyText(f.filteredParseStack()) + '\n';
558
 
    return ret;
559
 
};
560
 
 
561
 
 
562
 
 
563
 
/**
564
 
 * Run the given test, printing the summary of results and any errors. If run
565
 
 * inside a web browser, it will try to print these things to the printer, so
566
 
 * don't use this in a web browser.
567
 
 *
568
 
 * @param test: The test to run.
569
 
 * @type test: L{Divmod.UnitTest.TestCase} or L{Divmod.UnitTest.TestSuite}
570
 
 */
571
 
Divmod.UnitTest.run = function run(test) {
572
 
    var result = Divmod.UnitTest.TestResult()
573
 
    test.run(result);
574
 
    print(Divmod.UnitTest.formatErrors(result));
575
 
    print(Divmod.UnitTest.formatSummary(result));
576
 
};
577
 
 
578
 
 
579
 
Divmod.UnitTest.runRemote = function runRemote(test) {
580
 
    var result = Divmod.UnitTest.SubunitTestClient();
581
 
    test.run(result);
582
 
};
583
 
 
584
 
 
585
 
/**
586
 
 * Return a string representation of an arbitrary value, similar to
587
 
 * Python's builtin repr() function.
588
 
 */
589
 
Divmod.UnitTest.repr = function repr(value) {
590
 
    // We can't call methods on undefined or null.
591
 
    if (value === undefined) {
592
 
        return 'undefined';
593
 
    } else if (value === null) {
594
 
        return 'null';
595
 
    } else if (typeof value === 'string') {
596
 
        return '"' + value + '"';
597
 
    } else if (typeof value === 'number') {
598
 
        return '' + value;
599
 
    } else if (value.toSource !== undefined) {
600
 
        return value.toSource();
601
 
    } else if (value.toString !== undefined) {
602
 
        return value.toString();
603
 
    } else {
604
 
        return '' + value;
605
 
    }
606
 
};