Ticket #4651: DocTest.patch

File DocTest.patch, 9.2 KB (added by wolfram, 5 years ago)

A much more stable version

  • testing/DocTest.js

     
     1dojo.provide("dojox.testing.DocTest"); 
     2 
     3dojo.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