[gjs] coverage: Handle numerous other expression types in coverage.js



commit 5072cd737c605436bb48bc24a606144105e6912e
Author: Sam Spilsbury <smspillaz gmail com>
Date:   Wed Jan 7 14:37:08 2015 +0800

    coverage: Handle numerous other expression types in coverage.js
    
    Certain expressions, like for (... in), for (... of), yield,
    array comprehensions, logical expressions (a || b), etc were not
    being traversed. These could contain function declarations. This
    fixes numerous cases where an assertion might be raised by the
    coverage machinery where the debugger enters a function it does
    not know about.
    
    Fixes #742535

 installed-tests/js/testCoverage.js |  302 +++++++++++++++++++++++++++++++++++-
 modules/coverage.js                |   68 +++++++--
 test/gjs-test-coverage.cpp         |   42 +++++
 3 files changed, 397 insertions(+), 15 deletions(-)
---
diff --git a/installed-tests/js/testCoverage.js b/installed-tests/js/testCoverage.js
index 30b0da1..f22c3e9 100644
--- a/installed-tests/js/testCoverage.js
+++ b/installed-tests/js/testCoverage.js
@@ -34,7 +34,6 @@ function testExpressionLinesFoundForAssignmentExpressionSides() {
                       JSUnit.assertEquals);
 }
 
-
 function testExpressionLinesFoundForLinesInsideFunctions() {
     let foundLinesInsideNamedFunction =
         parseScriptForExpressionLines("function f(a, b) {\n" +
@@ -183,7 +182,7 @@ function testExpressionLinesFoundForIfExits() {
                       JSUnit.assertEquals);
 }
 
-function testExpressionLinesFoundForFirstLineOfMultilineIfTests() {
+function testExpressionLinesFoundForAllLinesOfMultilineIfTests() {
     let foundLinesInsideMultilineIfTest =
         parseScriptForExpressionLines("if (1 > 0 &&\n" +
                                       "    2 > 0 &&\n" +
@@ -191,7 +190,7 @@ function testExpressionLinesFoundForFirstLineOfMultilineIfTests() {
                                       "    let a = 3;\n" +
                                       "}\n");
     assertArrayEquals(foundLinesInsideMultilineIfTest,
-                      [1, 4],
+                      [1, 2, 3, 4],
                       JSUnit.assertEquals);
 }
 
@@ -331,6 +330,303 @@ function testFunctionsFoundOnSameLineButDifferentiatedOnArgs() {
                       functionDeclarationsEqual);
 }
 
+function testFunctionsInsideArrayExpression() {
+    let foundFunctions =
+        parseScriptForFunctionNames("let a = [function() {}];\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 },
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideArrowExpression() {
+    let foundFunctions =
+        parseScriptForFunctionNames("(a) => (function(){})();\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideSequence() {
+    let foundFunctions =
+        parseScriptForFunctionNames("(function(a){})()," +
+                                    "(function(a, b){})();\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 1 },
+                          { name: null, line: 1, n_params: 2 },
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideUnaryExpression() {
+    let foundFunctions =
+        parseScriptForFunctionNames("let a = (function () {}())++;\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 },
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideBinaryExpression() {
+    let foundFunctions =
+        parseScriptForFunctionNames("let a = function (a) {}() +" +
+                                    " function (a, b) {}();\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 1 },
+                          { name: null, line: 1, n_params: 2 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideAssignmentExpression() {
+    let foundFunctions =
+        parseScriptForFunctionNames("let a = function () {}();\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideUpdateExpression() {
+    let foundFunctions =
+        parseScriptForFunctionNames("let a;\n" +
+                                    "a += function () {}();\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 2, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideIfConditions() {
+    let foundFunctions =
+        parseScriptForFunctionNames("if (function (a) {}(a) >" +
+                                    "    function (a, b) {}(a, b)) {};\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 1 },
+                          { name: null, line: 1, n_params: 2 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideWhileConditions() {
+    let foundFunctions =
+        parseScriptForFunctionNames("while (function (a) {}(a) >" +
+                                    "       function (a, b) {}(a, b)) {};\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 1 },
+                          { name: null, line: 1, n_params: 2 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideForInitializer() {
+    let foundFunctions =
+        parseScriptForFunctionNames("for (function() {};;) {}\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+/* SpiderMonkey parses for (let i = <init>; <cond>; <update>) as though
+ * they were let i = <init> { for (; <cond> <update>) } so test the
+ * LetStatement initializer case too */
+function testFunctionsInsideForLetInitializer() {
+    let foundFunctions =
+        parseScriptForFunctionNames("for (let i = function() {};;) {}\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideForVarInitializer() {
+    let foundFunctions =
+        parseScriptForFunctionNames("for (var i = function() {};;) {}\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideForCondition() {
+    let foundFunctions =
+        parseScriptForFunctionNames("for (; function() {}();) {}\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideForIncrement() {
+    let foundFunctions =
+        parseScriptForFunctionNames("for (;;function() {}()) {}\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideForInObject() {
+    let foundFunctions =
+        parseScriptForFunctionNames("for (let x in function() {}()) {}\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideForInObject() {
+    let foundFunctions =
+        parseScriptForFunctionNames("for each (x in function() {}()) {}\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInsideForOfObject() {
+    let foundFunctions =
+        parseScriptForFunctionNames("for (x of (function(){}())) {}\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsUsedAsObjectFound() {
+    let foundFunctions =
+        parseScriptForFunctionNames("f = function() { }.bind();\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsUsedAsObjectDynamicProp() {
+    let foundFunctions =
+        parseScriptForFunctionNames("f = function() { }['bind']();\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsOnEitherSideOfLogicalExpression() {
+    let foundFunctions =
+        parseScriptForFunctionNames("let f = function(a) { } ||" +
+                                    " function(a, b) { };\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 1, n_params: 1 },
+                          { name: null, line: 1, n_params: 2 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsOnEitherSideOfConditionalExpression() {
+    let foundFunctions =
+        parseScriptForFunctionNames("let a\n" +
+                                    "let f = a ? function(a) { }() :" +
+                                    " function(a, b) { }();\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 2, n_params: 1 },
+                          { name: null, line: 2, n_params: 2 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsYielded() {
+    let foundFunctions =
+        parseScriptForFunctionNames("function a () { yield function (){} };\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: "a", line: 1, n_params: 0 },
+                          { name: null, line: 1, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInArrayComprehensionBody() {
+    let foundFunctions =
+        parseScriptForFunctionNames("let a = new Array(1);\n" +
+                                    "let b = [function () {} for (i of a)];\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 2, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInArrayComprehensionBlock() {
+    let foundFunctions =
+        parseScriptForFunctionNames("let a = new Array(1);\n" +
+                                    "let b = [i for (i of function () {})];\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 2, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
+function testFunctionsInArrayComprehensionFilter() {
+    let foundFunctions =
+        parseScriptForFunctionNames("let a = new Array(1);\n" +
+                                    "let b = [i for (i of a)" +
+                                    "if (function () {} ())];\n");
+
+    assertArrayEquals(foundFunctions,
+                      [
+                          { name: null, line: 2, n_params: 0 }
+                      ],
+                      functionDeclarationsEqual);
+}
+
 function parseScriptForBranches(script) {
     const ast = Reflect.parse(script);
     return Coverage.branchesForAST(ast);
diff --git a/modules/coverage.js b/modules/coverage.js
index 1c29992..4609069 100644
--- a/modules/coverage.js
+++ b/modules/coverage.js
@@ -28,15 +28,15 @@ function getSubNodesForNode(node) {
     /* These statements have a single body */
     case 'LabelledStatement':
     case 'WithStatement':
-    case 'LetStatement':
-    case 'ForInStatement':
-    case 'ForOfStatement':
     case 'FunctionDeclaration':
     case 'FunctionExpression':
-    case 'ArrowExpression':
     case 'CatchClause':
         subNodes.push(node.body);
         break;
+    case 'LetStatement':
+        Array.prototype.push.apply(subNodes, node.head);
+        subNodes.push(node.body);
+        break;
     case 'WhileStatement':
     case 'DoWhileStatement':
         subNodes.push(node.body);
@@ -52,11 +52,21 @@ function getSubNodesForNode(node) {
 
         subNodes.push(node.body);
         break;
+    case 'ForInStatement':
+        if (node.each)
+            subNodes.push(node.left);
+
+        subNodes.push(node.right, node.body);
+        break;
+    case 'ForOfStatement':
+        subNodes.push(node.left, node.right, node.body);
+        break;
     case 'BlockStatement':
         Array.prototype.push.apply(subNodes, node.body);
         break;
     case 'ThrowStatement':
     case 'ReturnStatement':
+    case 'YieldExpression':
         if (node.argument !== null)
             subNodes.push(node.argument);
         break;
@@ -64,13 +74,45 @@ function getSubNodesForNode(node) {
         subNodes.push(node.expression);
         break;
     case 'AssignmentExpression':
+    case 'BinaryExpression':
+    case 'LogicalExpression':
         subNodes.push(node.left, node.right);
         break;
+    case 'ConditionalExpression':
+        subNodes.push(node.test, node.consequent, node.alternate);
+        break;
     case 'ObjectExpression':
         node.properties.forEach(function(prop) {
             subNodes.push(prop.value);
         });
         break;
+    case 'ArrayExpression':
+        node.elements.forEach(function(elem) {
+            if (elem !== null)
+                subNodes.push(elem);
+        });
+        break;
+    case 'ArrowExpression':
+        Array.prototype.push.apply(subNodes, node.defaults);
+        subNodes.push(node.body);
+        break;
+    case 'SequenceExpression':
+        Array.prototype.push.apply(subNodes, node.expressions);
+        break;
+    case 'UnaryExpression':
+    case 'UpdateExpression':
+        subNodes.push(node.argument);
+        break;
+    case 'ComprehensionExpression':
+    case 'GeneratorExpression':
+        subNodes.push(node.body);
+        Array.prototype.push.apply(subNodes, node.blocks);
+        if (node.filter !== null)
+            subNodes.push(node.filter);
+        break;
+    case 'ComprehensionBlock':
+        subNodes.push(node.right);
+        break;
     /* It is very possible that there might be something
      * interesting in the function arguments, so we need to
      * walk them too */
@@ -99,16 +141,18 @@ function getSubNodesForNode(node) {
                 subNodes.push(expression);
             });
         }
-
         break;
-    /* Variable declarations might be initialized to
-     * some expression, so traverse the tree and see if
-     * we can get into the expression */
     case 'VariableDeclaration':
-        node.declarations.forEach(function (declarator) {
-            if (declarator.init !== null)
-                subNodes.push(declarator.init);
-        });
+        Array.prototype.push.apply(subNodes, node.declarations);
+        break;
+    case 'VariableDeclarator':
+        if (node.init !== null)
+            subNodes.push(node.init);
+        break;
+    case 'MemberExpression':
+        subNodes.push(node.object);
+        if (node.computed)
+            subNodes.push(node.property);
 
         break;
     }
diff --git a/test/gjs-test-coverage.cpp b/test/gjs-test-coverage.cpp
index 28929a7..de4a4e8 100644
--- a/test/gjs-test-coverage.cpp
+++ b/test/gjs-test-coverage.cpp
@@ -1157,6 +1157,44 @@ test_single_line_hit_written_to_coverage_data(gpointer      fixture_data,
 }
 
 static void
+test_hits_on_multiline_if_cond(gpointer      fixture_data,
+                                gconstpointer user_data)
+{
+    GjsCoverageToSingleOutputFileFixture *fixture = (GjsCoverageToSingleOutputFileFixture *) fixture_data;
+
+    const char *script_with_multine_if_cond =
+            "let a = 1;\n"
+            "let b = 1;\n"
+            "if (a &&\n"
+            "    b) {\n"
+            "}\n";
+
+    write_to_file_at_beginning(fixture->base_fixture.temporary_js_script_open_handle,
+                               script_with_multine_if_cond);
+
+    char *coverage_data_contents =
+        eval_script_and_get_coverage_data(fixture->base_fixture.context,
+                                          fixture->base_fixture.coverage,
+                                          fixture->base_fixture.temporary_js_script_filename,
+                                          fixture->output_file_directory,
+                                          NULL);
+
+    /* Hits on all lines, including both lines with a condition (3 and 4) */
+    LineCountIsMoreThanData data[] = {
+        { 1, 0 },
+        { 2, 0 },
+        { 3, 0 },
+        { 4, 0 }
+    };
+
+    g_assert(coverage_data_matches_value_for_key(coverage_data_contents,
+                                                 "DA:",
+                                                 line_hit_count_is_more_than,
+                                                 data));
+    g_free(coverage_data_contents);
+}
+
+static void
 test_full_line_tally_written_to_coverage_data(gpointer      fixture_data,
                                               gconstpointer user_data)
 {
@@ -1512,6 +1550,10 @@ void gjs_test_add_tests_for_coverage()
                          &coverage_to_single_output_fixture,
                          test_single_line_hit_written_to_coverage_data,
                          NULL);
+    add_test_for_fixture("/gjs/coverage/hits_on_multiline_if_cond",
+                         &coverage_to_single_output_fixture,
+                         test_hits_on_multiline_if_cond,
+                         NULL);
     add_test_for_fixture("/gjs/coverage/full_line_tally_written_to_coverage_data",
                          &coverage_to_single_output_fixture,
                          test_full_line_tally_written_to_coverage_data,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]