| | 1 | dojo.provide("dojox.testing.DocTest"); |
| | 2 | |
| | 3 | dojo.declare("dojox.testing.DocTest", null, { |
| | 4 | // summary: |
| | 5 | // This class executes doctests. |
| | 6 | // DocTests are tests that are defined inside the comment. |
| | 7 | // A doctest looks as if it was copied from the shell (which it mostly is). |
| | 8 | // A doctest is executed when the following conditions match: |
| | 9 | // 1) all lines are comments |
| | 10 | // 2) the line always starts with any number of spaces followed by "//" |
| | 11 | // and at least one space |
| | 12 | // 3) the line(s) of the test to execute starts with ">>>" |
| | 13 | // preceeded by what is described in 2) |
| | 14 | // 4) the first line after 3) starting without ">>>" is the exptected result. |
| | 15 | // preceeded by what is described in 2) |
| | 16 | // 5) the test sequence is terminated by an empty line, or the next |
| | 17 | // test in the following line, or a new line that does not start as described in 2) |
| | 18 | // (simple said: is not a comment) |
| | 19 | // preceeded by what is described in 2) |
| | 20 | // |
| | 21 | // I.e. the following is a simple doctest, that will actually also be run |
| | 22 | // if you run this class against this file here: |
| | 23 | // >>> 1+1 // A simple test case. Terminated by an empty line |
| | 24 | // 2 |
| | 25 | // |
| | 26 | // >>> 1==2 |
| | 27 | // false |
| | 28 | // >>> "a"+"b" // Also without the empty line before, this is a new test. |
| | 29 | // "ab" |
| | 30 | // |
| | 31 | // >>> var anything = "anything" // Multiple commands for one test. |
| | 32 | // >>> "something"==anything |
| | 33 | // false |
| | 34 | // |
| | 35 | // DocTests are great for inline documenting a class or method, they also |
| | 36 | // are very helpful in understanding what the class/method actually does. |
| | 37 | // The don't make sense in every place, but sometimes they are really handy. |
| | 38 | // TODO |
| | 39 | // - using console.log() in a test prints something on the console (if you do it on the console) |
| | 40 | // but its not accepted yet to be the test result, may be override console.log!? |
| | 41 | // i.e. here i wanted to: dojo.forEach(["one", 2], function(el, index) {console.log(el, index)}) |
| | 42 | // that works on the console, but not as a docTest :-( |
| | 43 | // - surround the eval for each test case singlely with a try-catch, to |
| | 44 | // to catch syntax errors etc (though the shouldn't happen if you copy the test from the shell :-)) |
| | 45 | |
| | 46 | |
| | 47 | errors:[], |
| | 48 | |
| | 49 | run:function(moduleName) { |
| | 50 | // summary: |
| | 51 | // Run the doctests in the module given. |
| | 52 | // examples: |
| | 53 | // doctest = new dojox.testing.DocTest(); |
| | 54 | // doctest.run("dojox.testing.DocTest"); |
| | 55 | // doctest.errors should finally be an empty array. |
| | 56 | // // The above is not a doctest, because it just would execute itself in a never ending loop. |
| | 57 | // |
| | 58 | // >>> true==true // Test a new line terminating the test. |
| | 59 | // true |
| | 60 | // |
| | 61 | // >>> true==true // Test a new test terminating the test. |
| | 62 | // true |
| | 63 | // >>> true==true // Test a "not a comment"-line, especially an empty line terminating the test. |
| | 64 | // true |
| | 65 | |
| | 66 | // Make sure the result as printed on the console is the same as what |
| | 67 | // is returned by the test. An array is printed as follows on the console. |
| | 68 | // >>> [1,2,3,4] |
| | 69 | // [1, 2, 3, 4] |
| | 70 | // |
| | 71 | // Test a "not a comment"-line, with some real code(!) terminating the test. |
| | 72 | // This used to be a bug, so make sure the line below the test is real |
| | 73 | // from this method! Don't write a test after it, always above! |
| | 74 | // >>> true==true // Test code on new line terminating the test. |
| | 75 | // true |
| | 76 | this._errors = []; |
| | 77 | var path = dojo.moduleUrl(moduleName).path; |
| | 78 | // TODO this needs to be done better, this is pretty simple and surely not dummy proof |
| | 79 | var file = path.substring(0, path.length-1)+".js"; |
| | 80 | var xhr = dojo.xhrGet({url:file, handleAs:"text"}); |
| | 81 | var _this = this; |
| | 82 | xhr.addCallback(function(data) { |
| | 83 | var lines = data.split("\n"); |
| | 84 | var len = lines.length; |
| | 85 | var tests = []; |
| | 86 | var test = {commands:[], expectedResult:[]}; |
| | 87 | for (var i=0; i<len; i++) { |
| | 88 | // Trim the line, so we don't have to worry about leading spaces or tabs, bla bla ... |
| | 89 | var l = dojo.string.trim(lines[i]); |
| | 90 | // TODO detect tests that dont match the condition: commands, result, empty line. esp the empty line might be missing |
| | 91 | // or be tolerant and accept a new test starting on the next line, which would allow to omit the empty line!? |
| | 92 | if (l.match(/^\/\/\s+>>>\s.*/)) { |
| | 93 | // Find the test commands. |
| | 94 | if (test.expectedResult.length>0) { |
| | 95 | // Start a new test right after the expected result, without an empty line. |
| | 96 | tests.push({commands:test.commands, expectedResult:test.expectedResult.join("\n")}); |
| | 97 | test = {commands:[], expectedResult:[]}; |
| | 98 | } |
| | 99 | l = dojo.trim(l).substring(2, l.length); // Remove the leading slashes. |
| | 100 | l = dojo.trim(l).substring(3, l.length); // Remove the ">>>". |
| | 101 | test.commands.push(dojo.trim(l)); |
| | 102 | } else if (l.match(/^\/\/\s+.*/) && test.commands.length>0) { |
| | 103 | // Detect the lines after the ">>>"-lines, the exptected result. |
| | 104 | l = dojo.trim(l).substring(3, l.length); // Remove the leading slashes. |
| | 105 | test.expectedResult.push(dojo.trim(l)); |
| | 106 | } else if (test.commands.length>0 && test.expectedResult.length>0) { |
| | 107 | if (l.match(/^\/\/\s*$/)) { |
| | 108 | // Detect the empty line. |
| | 109 | tests.push({commands:test.commands, expectedResult:test.expectedResult.join("\n")}); |
| | 110 | } |
| | 111 | if (!l.match(/^\/\//)) { |
| | 112 | // If the next line is not a comment at all (doesnt start with "//"). |
| | 113 | tests.push({commands:test.commands, expectedResult:test.expectedResult.join("\n")}); |
| | 114 | } |
| | 115 | test = {commands:[], expectedResult:[]}; |
| | 116 | } |
| | 117 | } |
| | 118 | if (tests) { |
| | 119 | _this._run(tests); |
| | 120 | } |
| | 121 | }); |
| | 122 | }, |
| | 123 | |
| | 124 | _run:function(/* Array */tests) { |
| | 125 | // summary: |
| | 126 | // Each element in the array contains the test in the first element, |
| | 127 | // and the expected result in the second element. |
| | 128 | // |
| | 129 | // tests: |
| | 130 | // Make sure that the types are compared properly. There used to be |
| | 131 | // the bug that a return value false was compared to "false" which |
| | 132 | // made the test fail. This is fixed and should be verified by the |
| | 133 | // following tests. |
| | 134 | // >>> false |
| | 135 | // false |
| | 136 | // |
| | 137 | // >>> "false" |
| | 138 | // "false" |
| | 139 | // |
| | 140 | // >>> true |
| | 141 | // true |
| | 142 | // |
| | 143 | // >>> 1 |
| | 144 | // 1 |
| | 145 | // |
| | 146 | // >>> "s" |
| | 147 | // "s" |
| | 148 | // |
| | 149 | // >>> dojo.toJson({one:1}) |
| | 150 | // "{"one": 1}" |
| | 151 | // |
| | 152 | var len = tests.length; |
| | 153 | this.tests = len; |
| | 154 | for (var i=0; i<len; i++) { |
| | 155 | var t = tests[i]; |
| | 156 | var commands = ""; |
| | 157 | for (var j=0; j<t.commands.length; j++) { |
| | 158 | // Concat multiple commands with new lines, so "//" comments at |
| | 159 | // the end of a line don't deactivate the next line (which it |
| | 160 | // would if we only concatenated with ";"). |
| | 161 | commands += t.commands[j]+"\n"; |
| | 162 | } |
| | 163 | var actualResult = eval(commands); |
| | 164 | var msg = "Test "+(i+1)+": "; |
| | 165 | var expected = t.expectedResult; |
| | 166 | var viewCommand = (commands.length>50 ? commands.substr(0,50)+"..." : commands); // Show the first part of the test command. |
| | 167 | viewCommand = viewCommand.replace("\n", " "); // Make it show one line only (nice for the console). |
| | 168 | if (String(actualResult)==expected || dojo.toJson(actualResult)==expected || |
| | 169 | (expected.charAt(0)=='"' && expected.charAt(expected.length-1)=='"' && String(actualResult)==expected.substring(1, expected.length-1)) |
| | 170 | ) { |
| | 171 | // the last if-condition, dojo.toJson() adds a quote sign " before and after the result, may be we remove it and test the result again |
| | 172 | console.info(msg+"OK: "+viewCommand); |
| | 173 | } else { |
| | 174 | this.errors.push({commands:commands, actual:actualResult, expected:t.expectedResult}); |
| | 175 | console.error(msg+"Failed: "+viewCommand, {commands:commands, actualResult:actualResult, expectedResult:t.expectedResult}); |
| | 176 | } |
| | 177 | } |
| | 178 | } |
| | 179 | }); |
| | 180 | No newline at end of file |