source: Dev/branches/rest-dojo-ui/client/dojox/testing/DocTest.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 9.5 KB
Line 
1define("dojox/testing/DocTest", ["dojo/string"], function() {
2
3dojo.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: Extract the tests from the given module or string.
60                        // examples:
61                        //              >>> dojo.isArray(new dojox.testing.DocTest().getTests("dojox.testing.DocTest")) // Use the module name to extract the tests from.
62                        //              true
63                        var path = dojo.moduleUrl(moduleName).path;
64                        // TODO:
65                        //              this needs to be done better, this is pretty simple and
66                        //              surely not dummy proof
67                        var file = path.substring(0, path.length-1)+".js";
68                        var xhr = dojo.xhrGet({url:file, handleAs:"text"});
69                        // Make loading synchronously, mainly so we can use it in doh.
70                        var data = dojo._getText(file);
71                        return this._getTestsFromString(data, true);
72                },
73               
74                getTestsFromString:function(/*String*/data){
75                        // examples:
76                        //              >>> (new dojox.testing.DocTest().getTestsFromString(">>> 1+1\n2\n>>> 2+2\n4")).length // Do tests from strings get detected properly?
77                        //              2
78                        return this._getTestsFromString(data, false);
79                },
80               
81                _getTestsFromString:function(/*String*/data, /*Boolean*/insideComments){
82                        // summary: Parse the given string for tests.
83                        // insideComments: Boolean, if false "data" contains only the pure tests, comments already stripped.
84                        var trim = dojo.hitch(dojo.string, "trim");
85                        var lines = data.split("\n");
86                        var len = lines.length;
87                        var tests = [];
88                        var test = {
89                                commands: [],
90                                expectedResult: [],
91                                line: null
92                        };
93                        for(var i=0; i<len+1; i++){
94                                // Trim the line, so we don't have to worry about leading
95                                // spaces or tabs, bla bla ...
96                                var l = trim(lines[i] || ""); // The '|| ""' makes sure tests that have no preceeding \n are taken into account too.
97                                // TODO:
98                                //              detect tests that dont match the condition: commands,
99                                //              result, empty line. esp the empty line might be missing
100                                //              or be tolerant and accept a new test starting on the
101                                //              next line, which would allow to omit the empty line!?
102                                if((insideComments && l.match(/^\/\/\s+>>>\s.*/)) || l.match(/^\s*>>>\s.*/)){
103                                        if(!test.line){
104                                                test.line = i+1;
105                                        }
106                                        // Find the test commands.
107                                        if(test.expectedResult.length>0){
108                                                // Start a new test right after the expected result,
109                                                // without an empty line.
110                                                tests.push({
111                                                        commands: test.commands,
112                                                        expectedResult: test.expectedResult.join("\n"),
113                                                        line: test.line
114                                                });
115                                                test = {commands:[], expectedResult:[], line:i+1};
116                                        }
117                                        l = insideComments ? trim(l).substring(2, l.length) : l; // Remove the leading slashes.
118                                        l = trim(l).substring(3, l.length); // Remove the ">>>".
119                                        test.commands.push(trim(l));
120                                }else if((!insideComments || l.match(/^\/\/\s+.*/)) && test.commands.length && test.expectedResult.length==0){
121                                        // Detect the lines after the ">>>"-lines, the exptected result.
122                                        l = insideComments ? trim(l).substring(3, l.length) : l; // Remove the leading slashes.
123                                        test.expectedResult.push(trim(l));
124                                }else if(test.commands.length>0 && test.expectedResult.length){
125                                        if(!insideComments || l.match(/^\/\/\s*$/)){
126                                                // Detect the empty line.
127                                                tests.push({
128                                                        commands: test.commands,
129                                                        expectedResult: test.expectedResult.join("\n"),
130                                                        line: test.line
131                                                });
132                                        }
133                                        if(insideComments && !l.match(/^\/\//)){
134                                                // If the next line is not a comment at all (doesn't start with "//").
135                                                tests.push({
136                                                        commands: test.commands,
137                                                        expectedResult: test.expectedResult.join("\n"),
138                                                        line:test.line
139                                                });
140                                        }
141                                        test = {
142                                                commands: [],
143                                                expectedResult: [],
144                                                line:0
145                                        };
146                                }
147                        }
148                        return tests;
149                },
150               
151                run: function(moduleName){
152                        //      summary:
153                        //              Run the doctests in the module given.
154                        //      example:
155                        //              doctest = new dojox.testing.DocTest();
156                        //              doctest.run("dojox.testing.DocTest");
157                        //              doctest.errors should finally be an empty array.
158                        //              // The above is not a doctest, because it just would
159                        //              //      execute itself in a never ending loop.
160                        //
161                        //              >>> true==true // Test a new line terminating the test.
162                        //              true
163                        //
164                        //              >>> true==true // Test a new test terminating the test.
165                        //              true
166                        //              >>> true==true // Test a "not a comment"-line, especially an empty line terminating the test.
167                        //              true
168
169                        //              Make sure the result as printed on the console is the same as what
170                        //              is returned by the test. An array is printed as follows on the console.
171                        //              >>> [1,2,3,4]
172                        //              [1,2,3,4]
173                        //
174                        //              Test a "not a comment"-line, with some real code(!) terminating the test.
175                        //              This used to be a bug, so make sure the line below the test is real
176                        //              from this method! Don't write a test after it, always above!
177                        //              >>> true==true // Test code on new line terminating the test.
178                        //              true
179       
180                        this.errors = [];
181                       
182                        var tests = this.getTests(moduleName);
183                        if(tests){
184                                this._run(tests);
185                        }
186                },
187               
188                _run: function(/*Array*/tests){
189                        //      summary:
190                        //              Each element in the array contains the test in the first element,
191                        //              and the expected result in the second element.
192                        //      tests:
193                        //              Make sure that the types are compared properly. There used to be
194                        //              the bug that a return value false was compared to "false" which
195                        //              made the test fail. This is fixed and should be verified by the
196                        //              following tests.
197                        //              >>> false
198                        //              false
199                        //
200                        //              >>> "false"
201                        //              "false"
202                        //
203                        //              >>> true
204                        //              true
205                        //
206                        //              >>> 1
207                        //              1
208                        //
209                        //              >>> "s"
210                        //              "s"
211                        //
212                        //              >>> dojo.toJson({one:1})
213                        //              "{"one":1}"
214                        //
215                        var len = tests.length;
216                        this.tests = len;
217                        var oks = 0;
218                        for(var i=0; i<len; i++){
219                                var t = tests[i];
220                                var res = this.runTest(t.commands, t.expectedResult);
221                                var msg = "Test "+(i+1)+": ";
222                                var viewCommands = t.commands.join(" ");
223                                // Show the first part of the test command.
224                                viewCommands = (viewCommands.length > 50 ?
225                                                                viewCommands.substr(0,50) + "..." :
226                                                                viewCommands
227                                );
228                                if(res.success){
229                                        // the last if-condition, dojo.toJson() adds a quote sign "
230                                        // before and after the result, may be we remove it and
231                                        // test the result again
232                                        console.info(msg+"OK: "+viewCommands);
233                                        oks += 1;
234                                }else{
235                                        this.errors.push({
236                                                commands: t.commands,
237                                                actual: res.actualResult,
238                                                expected: t.expectedResult
239                                        });
240                                        console.error(msg+"Failed: "+viewCommands, {
241                                                commands: t.commands,
242                                                actualResult: res.actualResult,
243                                                expectedResult: t.expectedResult
244                                        });
245                                }
246                        }
247                        console.info(len+" tests ran.", oks+" Success,", this.errors.length+" Errors");
248                },
249               
250                runTest: function(commands, expected){
251                        var ret = {
252                                success: false,
253                                actualResult: null
254                        };
255                        // Concat multiple commands with new lines, so "//" comments at
256                        // the end of a line don't deactivate the next line (which it
257                        // would if we only concatenated with ";").
258                        var cmds = commands.join("\n");
259                        ret.actualResult = eval(cmds);
260                        if( (String(ret.actualResult)==expected) ||
261                                (dojo.toJson(ret.actualResult)==expected) ||
262                                (
263                                        (expected.charAt(0)=='"')&&
264                                        (expected.charAt(expected.length-1)=='"')&&
265                                        (String(ret.actualResult)==expected.substring(1, expected.length-1))
266                                )
267                        ){
268                                // the last if-condition, dojo.toJson() adds a quote sign "
269                                // before and after the result, may be we remove it and test
270                                // the result again
271                                ret.success = true;
272                        }
273                        return ret;
274                }
275        }
276);
277
278return dojox.testing.DocTest;
279});
Note: See TracBrowser for help on using the repository browser.