source: Dev/trunk/src/client/dojox/testing/DocTest.js @ 483

Last change on this file since 483 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 9.5 KB
Line 
1define("dojox/testing/DocTest", ["dojo/string"], function() {
2
3return dojo.declare(
4        "dojox.testing.DocTest",
5        null,
6        {
7                // summary:
8                //              This class executes doctests.
9                // description:
10                //              DocTests are tests that are defined inside the comment.
11                //              A doctest looks as if it was copied from the shell (which it mostly is).
12                //              A doctest is executed when the following conditions match:
13                //              1) all lines are comments
14                //              2) the line always starts with spaces/tabs followed by "//"
15                //                 and at least one space
16                //              3) the line(s) of the test to execute starts with ">>>"
17                //                 preceeded by what is described in 2)
18                //              4) the first line after 3) starting without ">>>" is the exptected result.
19                //                 preceeded by what is described in 2)
20                //              5) the test sequence is terminated by an empty line, or the next
21                //                 test in the following line, or a new line that does not start as described in 2)
22                //                 (simple said: is not a comment)
23                //                 preceeded by what is described in 2)
24                //
25                //              I.e. the following is a simple doctest, that will actually also be run
26                //              if you run this class against this file here:
27                //              >>> 1+1 // A simple test case. Terminated by an empty line
28                //              2
29                //
30                //              >>> 1==2
31                //              false
32                //              >>> "a"+"b" // Also without the empty line before, this is a new test.
33                //              "ab"
34                //
35                //              >>> var anything = "anything" // Multiple commands for one test.
36                //              >>> "something"==anything
37                //              false
38                //
39                //              DocTests are great for inline documenting a class or method, they also
40                //              are very helpful in understanding what the class/method actually does.
41                //              They don't make sense everywhere, but sometimes they are really handy.
42
43
44                //      TODO:
45                //              -       using console.log() in a test prints something on the
46                //                      console (if you do it on the console) but its not accepted
47                //                      yet to be the test result, may be override console.log!?
48                //                      i.e. here i wanted to: dojo.forEach(["one", 2],
49                //                      function(el, index) {console.log(el, index)}) that works on
50                //                      the console, but not as a docTest :-(
51                //              -       surround the eval for each test case singlely with a
52                //                      try-catch, to to catch syntax errors etc (though the
53                //                      shouldn't happen if you copy the test from the shell :-))
54       
55               
56                errors: [],
57               
58                getTests:function(/*String*/moduleName){
59                        // summary:
60                        //              Extract the tests from the given module or string.
61                        // examples:
62                        //              >>> dojo.isArray(new dojox.testing.DocTest().getTests("dojox.testing.DocTest")) // Use the module name to extract the tests from.
63                        //              true
64                        var path = dojo.moduleUrl(moduleName).path;
65                        // TODO:
66                        //              this needs to be done better, this is pretty simple and
67                        //              surely not dummy proof
68                        var file = path.substring(0, path.length-1)+".js";
69                        var xhr = dojo.xhrGet({url:file, handleAs:"text"});
70                        // Make loading synchronously, mainly so we can use it in doh.
71                        var data = dojo._getText(file);
72                        return this._getTestsFromString(data, true);
73                },
74               
75                getTestsFromString:function(/*String*/data){
76                        // examples:
77                        //              >>> (new dojox.testing.DocTest().getTestsFromString(">>> 1+1\n2\n>>> 2+2\n4")).length // Do tests from strings get detected properly?
78                        //              2
79                        return this._getTestsFromString(data, false);
80                },
81               
82                _getTestsFromString:function(/*String*/data, /*Boolean*/insideComments){
83                        // summary:
84                        //              Parse the given string for tests.
85                        // insideComments: Boolean
86                        //              if false "data" contains only the pure tests, comments already stripped.
87                        var trim = dojo.hitch(dojo.string, "trim");
88                        var lines = data.split("\n");
89                        var len = lines.length;
90                        var tests = [];
91                        var test = {
92                                commands: [],
93                                expectedResult: [],
94                                line: null
95                        };
96                        for(var i=0; i<len+1; i++){
97                                // Trim the line, so we don't have to worry about leading
98                                // spaces or tabs, bla bla ...
99                                var l = trim(lines[i] || ""); // The '|| ""' makes sure tests that have no preceeding \n are taken into account too.
100                                // TODO:
101                                //              detect tests that dont match the condition: commands,
102                                //              result, empty line. esp the empty line might be missing
103                                //              or be tolerant and accept a new test starting on the
104                                //              next line, which would allow to omit the empty line!?
105                                if((insideComments && l.match(/^\/\/\s+>>>\s.*/)) || l.match(/^\s*>>>\s.*/)){
106                                        if(!test.line){
107                                                test.line = i+1;
108                                        }
109                                        // Find the test commands.
110                                        if(test.expectedResult.length>0){
111                                                // Start a new test right after the expected result,
112                                                // without an empty line.
113                                                tests.push({
114                                                        commands: test.commands,
115                                                        expectedResult: test.expectedResult.join("\n"),
116                                                        line: test.line
117                                                });
118                                                test = {commands:[], expectedResult:[], line:i+1};
119                                        }
120                                        l = insideComments ? trim(l).substring(2, l.length) : l; // Remove the leading slashes.
121                                        l = trim(l).substring(3, l.length); // Remove the ">>>".
122                                        test.commands.push(trim(l));
123                                }else if((!insideComments || l.match(/^\/\/\s+.*/)) && test.commands.length && test.expectedResult.length==0){
124                                        // Detect the lines after the ">>>"-lines, the exptected result.
125                                        l = insideComments ? trim(l).substring(3, l.length) : l; // Remove the leading slashes.
126                                        test.expectedResult.push(trim(l));
127                                }else if(test.commands.length>0 && test.expectedResult.length){
128                                        if(!insideComments || l.match(/^\/\/\s*$/)){
129                                                // Detect the empty line.
130                                                tests.push({
131                                                        commands: test.commands,
132                                                        expectedResult: test.expectedResult.join("\n"),
133                                                        line: test.line
134                                                });
135                                        }
136                                        if(insideComments && !l.match(/^\/\//)){
137                                                // If the next line is not a comment at all (doesn't start with "//").
138                                                tests.push({
139                                                        commands: test.commands,
140                                                        expectedResult: test.expectedResult.join("\n"),
141                                                        line:test.line
142                                                });
143                                        }
144                                        test = {
145                                                commands: [],
146                                                expectedResult: [],
147                                                line:0
148                                        };
149                                }
150                        }
151                        return tests;
152                },
153               
154                run: function(moduleName){
155                        // summary:
156                        //              Run the doctests in the module given.
157                        // example:
158                        //              doctest = new dojox.testing.DocTest();
159                        //              doctest.run("dojox.testing.DocTest");
160                        //              doctest.errors should finally be an empty array.
161                        //              // The above is not a doctest, because it just would
162                        //              //      execute itself in a never ending loop.
163                        //
164                        //              >>> true==true // Test a new line terminating the test.
165                        //              true
166                        //
167                        //              >>> true==true // Test a new test terminating the test.
168                        //              true
169                        //              >>> true==true // Test a "not a comment"-line, especially an empty line terminating the test.
170                        //              true
171
172                        //              Make sure the result as printed on the console is the same as what
173                        //              is returned by the test. An array is printed as follows on the console.
174                        //              >>> [1,2,3,4]
175                        //              [1,2,3,4]
176                        //
177                        //              Test a "not a comment"-line, with some real code(!) terminating the test.
178                        //              This used to be a bug, so make sure the line below the test is real
179                        //              from this method! Don't write a test after it, always above!
180                        //              >>> true==true // Test code on new line terminating the test.
181                        //              true
182       
183                        this.errors = [];
184                       
185                        var tests = this.getTests(moduleName);
186                        if(tests){
187                                this._run(tests);
188                        }
189                },
190               
191                _run: function(/*Array*/tests){
192                        // summary:
193                        //              Each element in the array contains the test in the first element,
194                        //              and the expected result in the second element.
195                        // tests:
196                        //              Make sure that the types are compared properly. There used to be
197                        //              the bug that a return value false was compared to "false" which
198                        //              made the test fail. This is fixed and should be verified by the
199                        //              following tests.
200                        //              >>> false
201                        //              false
202                        //
203                        //              >>> "false"
204                        //              "false"
205                        //
206                        //              >>> true
207                        //              true
208                        //
209                        //              >>> 1
210                        //              1
211                        //
212                        //              >>> "s"
213                        //              "s"
214                        //
215                        //              >>> dojo.toJson({one:1})
216                        //              "{"one":1}"
217                        //
218                        var len = tests.length;
219                        this.tests = len;
220                        var oks = 0;
221                        for(var i=0; i<len; i++){
222                                var t = tests[i];
223                                var res = this.runTest(t.commands, t.expectedResult);
224                                var msg = "Test "+(i+1)+": ";
225                                var viewCommands = t.commands.join(" ");
226                                // Show the first part of the test command.
227                                viewCommands = (viewCommands.length > 50 ?
228                                                                viewCommands.substr(0,50) + "..." :
229                                                                viewCommands
230                                );
231                                if(res.success){
232                                        // the last if-condition, dojo.toJson() adds a quote sign "
233                                        // before and after the result, may be we remove it and
234                                        // test the result again
235                                        console.info(msg+"OK: "+viewCommands);
236                                        oks += 1;
237                                }else{
238                                        this.errors.push({
239                                                commands: t.commands,
240                                                actual: res.actualResult,
241                                                expected: t.expectedResult
242                                        });
243                                        console.error(msg+"Failed: "+viewCommands, {
244                                                commands: t.commands,
245                                                actualResult: res.actualResult,
246                                                expectedResult: t.expectedResult
247                                        });
248                                }
249                        }
250                        console.info(len+" tests ran.", oks+" Success,", this.errors.length+" Errors");
251                },
252               
253                runTest: function(commands, expected){
254                        var ret = {
255                                success: false,
256                                actualResult: null
257                        };
258                        // Concat multiple commands with new lines, so "//" comments at
259                        // the end of a line don't deactivate the next line (which it
260                        // would if we only concatenated with ";").
261                        var cmds = commands.join("\n");
262                        ret.actualResult = eval(cmds);
263                        if( (String(ret.actualResult)==expected) ||
264                                (dojo.toJson(ret.actualResult)==expected) ||
265                                (
266                                        (expected.charAt(0)=='"')&&
267                                        (expected.charAt(expected.length-1)=='"')&&
268                                        (String(ret.actualResult)==expected.substring(1, expected.length-1))
269                                )
270                        ){
271                                // the last if-condition, dojo.toJson() adds a quote sign "
272                                // before and after the result, may be we remove it and test
273                                // the result again
274                                ret.success = true;
275                        }
276                        return ret;
277                }
278        }
279);
280
281});
Note: See TracBrowser for help on using the repository browser.