Merge branch 'MDL-63366-master' of git://github.com/andrewnicols/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 13 Mar 2019 23:31:39 +0000 (00:31 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 13 Mar 2019 23:31:39 +0000 (00:31 +0100)
admin/tool/dataprivacy/tests/coverage.php [new file with mode: 0644]
lib/phpunit/classes/coverage_info.php [new file with mode: 0644]
lib/phpunit/classes/util.php
lib/tests/coverage.php [new file with mode: 0644]
phpunit.xml.dist
privacy/tests/coverage.php [new file with mode: 0644]

diff --git a/admin/tool/dataprivacy/tests/coverage.php b/admin/tool/dataprivacy/tests/coverage.php
new file mode 100644 (file)
index 0000000..9af4b87
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Coverage information for the tool_dataprivacy plugin.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Coverage information for the tool_dataprivacy plugin.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+return new class extends phpunit_coverage_info {
+    /** @var array The list of folders relative to the plugin root to whitelist in coverage generation. */
+    protected $whitelistfolders = [
+        'classes',
+    ];
+
+    /** @var array The list of files relative to the plugin root to whitelist in coverage generation. */
+    protected $whitelistfiles = [];
+
+    /** @var array The list of folders relative to the plugin root to excludelist in coverage generation. */
+    protected $excludelistfolders = [
+    ];
+
+    /** @var array The list of files relative to the plugin root to excludelist in coverage generation. */
+    protected $excludelistfiles = [];
+};
diff --git a/lib/phpunit/classes/coverage_info.php b/lib/phpunit/classes/coverage_info.php
new file mode 100644 (file)
index 0000000..0eb20db
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Coverage information for PHPUnit.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Coverage information for PHPUnit.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class phpunit_coverage_info {
+
+    /** @var array The list of folders relative to the plugin root to whitelist in coverage generation. */
+    protected $whitelistfolders = [];
+
+    /** @var array The list of files relative to the plugin root to whitelist in coverage generation. */
+    protected $whitelistfiles = [];
+
+    /** @var array The list of folders relative to the plugin root to excludelist in coverage generation. */
+    protected $excludelistfolders = [];
+
+    /** @var array The list of files relative to the plugin root to excludelist in coverage generation. */
+    protected $excludelistfiles = [];
+
+    /**
+     * Get the formatted XML list of files and folders to whitelist.
+     *
+     * @param   string  $plugindir The root of the plugin, relative to the dataroot.
+     * @return  array
+     */
+    final public function get_whitelists(string $plugindir) : array {
+        $filters = [];
+
+        if (!empty($plugindir)) {
+            $plugindir .= "/";
+        }
+
+        foreach ($this->whitelistfolders as $folder) {
+            $filters[] = html_writer::tag('directory', "{$plugindir}{$folder}", ['suffix' => '.php']);
+        }
+
+        foreach ($this->whitelistfiles as $file) {
+            $filters[] = html_writer::tag('file', "{$plugindir}{$file}");
+        }
+
+        return $filters;
+    }
+
+    /**
+     * Get the formatted XML list of files and folders to exclude.
+     *
+     * @param   string  $plugindir The root of the plugin, relative to the dataroot.
+     * @return  array
+     */
+    final public function get_excludelists(string $plugindir) : array {
+        $filters = [];
+
+        if (!empty($plugindir)) {
+            $plugindir .= "/";
+        }
+
+        foreach ($this->excludelistfolders as $folder) {
+            $filters[] = html_writer::tag('directory', "{$plugindir}{$folder}", ['suffix' => '.php']);
+        }
+
+        foreach ($this->excludelistfiles as $file) {
+            $filters[] = html_writer::tag('file', "{$plugindir}{$file}");
+        }
+
+        return $filters;
+    }
+}
index 8c594f6..798b69c 100644 (file)
@@ -24,6 +24,7 @@
  */
 
 require_once(__DIR__.'/../../testing/classes/util.php');
+require_once(__DIR__ . "/coverage_info.php");
 
 /**
  * Collection of utility methods.
@@ -489,29 +490,62 @@ class phpunit_util extends testing_util {
         <testsuite name="@component@_testsuite">
             <directory suffix="_test.php">@dir@</directory>
         </testsuite>';
+        $filtertemplate = '
+        <testsuite name="@component@_testsuite">
+            <directory suffix="_test.php">@dir@</directory>
+        </testsuite>';
         $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
 
         $suites = '';
+        $whitelists = [];
+        $excludelists = [];
+
+        $subsystems = core_component::get_core_subsystems();
+        $subsystems['core'] = $CFG->dirroot . '/lib';
+        foreach ($subsystems as $subsystem => $fulldir) {
+            if (empty($fulldir)) {
+                continue;
+            }
+            if (!file_exists("{$fulldir}/tests/")) {
+                // There are no tests - skip this directory.
+                continue;
+            }
+
+            $dir = substr($fulldir, strlen($CFG->dirroot) + 1);
+            if ($coverageinfo = self::get_coverage_info($fulldir)) {
+                $whitelists = array_merge($whitelists, $coverageinfo->get_whitelists($dir));
+                $excludelists = array_merge($excludelists, $coverageinfo->get_excludelists($dir));
+            }
+        }
 
         $plugintypes = core_component::get_plugin_types();
         ksort($plugintypes);
-        foreach ($plugintypes as $type=>$unused) {
+        foreach (array_keys($plugintypes) as $type) {
             $plugs = core_component::get_plugin_list($type);
             ksort($plugs);
-            foreach ($plugs as $plug=>$fullplug) {
-                if (!file_exists("$fullplug/tests/")) {
+            foreach ($plugs as $plug => $plugindir) {
+                if (!file_exists("{$plugindir}/tests/")) {
+                    // There are no tests - skip this directory.
                     continue;
                 }
-                $dir = substr($fullplug, strlen($CFG->dirroot)+1);
-                $dir .= '/tests';
-                $component = $type.'_'.$plug;
+
+                $dir = substr($plugindir, strlen($CFG->dirroot) + 1);
+                $testdir = "{$dir}/tests";
+                $component = "{$type}_{$plug}";
 
                 $suite = str_replace('@component@', $component, $template);
-                $suite = str_replace('@dir@', $dir, $suite);
+                $suite = str_replace('@dir@', $testdir, $suite);
 
                 $suites .= $suite;
+
+                if ($coverageinfo = self::get_coverage_info($plugindir)) {
+
+                    $whitelists = array_merge($whitelists, $coverageinfo->get_whitelists($dir));
+                    $excludelists = array_merge($excludelists, $coverageinfo->get_excludelists($dir));
+                }
             }
         }
+
         // Start a sequence between 100000 and 199000 to ensure each call to init produces
         // different ids in the database.  This reduces the risk that hard coded values will
         // end up being placed in phpunit or behat test code.
@@ -523,6 +557,9 @@ class phpunit_util extends testing_util {
             '<const name="PHPUNIT_SEQUENCE_START" value="' . $sequencestart . '"/>',
             $data);
 
+        $filters = self::get_filter_config($whitelists, $excludelists);
+        $data = str_replace('<!--@filterlist@-->', $filters, $data);
+
         $result = false;
         if (is_writable($CFG->dirroot)) {
             if ($result = file_put_contents("$CFG->dirroot/phpunit.xml", $data)) {
@@ -551,19 +588,18 @@ class phpunit_util extends testing_util {
         global $CFG;
 
         $template = '
-        <testsuites>
-            <testsuite name="@component@_testsuite">
-                <directory suffix="_test.php">.</directory>
-            </testsuite>
-        </testsuites>
-        <filter>
+    <testsuites>
+        <testsuite name="@component@_testsuite">
+            <directory suffix="_test.php">.</directory>
+        </testsuite>
+    </testsuites>';
+        $filterdefault = '
             <whitelist processUncoveredFilesFromWhitelist="false">
                 <directory suffix=".php">.</directory>
                 <exclude>
                     <directory suffix="_test.php">.</directory>
                 </exclude>
-            </whitelist>
-        </filter>';
+            </whitelist>';
 
         // Start a sequence between 100000 and 199000 to ensure each call to init produces
         // different ids in the database.  This reduces the risk that hard coded values will
@@ -583,8 +619,17 @@ class phpunit_util extends testing_util {
             $ctemplate = $template;
             $ctemplate = str_replace('@component@', $cname, $ctemplate);
 
-            // Apply it to the file template
             $fcontents = str_replace('<!--@component_suite@-->', $ctemplate, $ftemplate);
+
+            // Check for filter configurations.
+            if ($coverageinfo = self::get_coverage_info($cpath)) {
+                $filters = self::get_filter_config($coverageinfo->get_whitelists(''), $coverageinfo->get_excludelists(''));
+            } else {
+                $filters = $filterdefault;
+            }
+            $fcontents = str_replace('<!--@filterlist@-->', $filters, $fcontents);
+
+            // Apply it to the file template.
             $fcontents = str_replace(
                 '<const name="PHPUNIT_SEQUENCE_START" value=""/>',
                 '<const name="PHPUNIT_SEQUENCE_START" value="' . $sequencestart . '"/>',
@@ -881,4 +926,62 @@ class phpunit_util extends testing_util {
         $method->setAccessible(true);
         return $method->invokeArgs($object, $params);
     }
+
+    /**
+     * Pad the supplied string with $level levels of indentation.
+     *
+     * @param   string  $string The string to pad
+     * @param   int     $level The number of levels of indentation to pad
+     * @return  string
+     */
+    protected static function pad(string $string, int $level) : string {
+        return str_repeat(" ", $level * 4) . "{$string}\n";
+    }
+
+    /**
+     * Get the filter config for the supplied whitelist and excludelist configuration.
+     *
+     * @param   array[] $whitelists The list of files/folders in the whitelist.
+     * @param   array[] $excludelists The list of files/folders in the excludelist.
+     * @return  string
+     */
+    protected static function get_filter_config(array $whitelists, array $excludelists) : string {
+        $filters = '';
+        if (!empty($whitelists)) {
+            $filters .= self::pad("<whitelist>", 2);
+            foreach ($whitelists as $line) {
+                $filters .= self::pad($line, 3);
+            }
+            if (!empty($excludelists)) {
+                $filters .= self::pad("<exclude>", 3);
+                foreach ($excludelists as $line) {
+                    $filters .= self::pad($line, 4);
+                }
+                $filters .= self::pad("</exclude>", 3);
+            }
+            $filters .= self::pad("</whitelist>", 2);
+        }
+
+        return $filters;
+    }
+
+    /**
+     * Get the phpunit_coverage_info for the specified plugin or subsystem directory.
+     *
+     * @param   string  $fulldir The directory to find the coverage info file in.
+     * @return  phpunit_coverage_info
+     */
+    protected static function get_coverage_info(string $fulldir): ?phpunit_coverage_info {
+        $coverageconfig = "{$fulldir}/tests/coverage.php";
+        if (file_exists($coverageconfig)) {
+            $coverageinfo = require($coverageconfig);
+            if (!$coverageinfo instanceof phpunit_coverage_info) {
+                throw new \coding_exception("{$coverageconfig} does not return a phpunit_coverage_info");
+            }
+
+            return $coverageinfo;
+        }
+
+        return null;
+    }
 }
diff --git a/lib/tests/coverage.php b/lib/tests/coverage.php
new file mode 100644 (file)
index 0000000..8251d45
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Coverage information for the core subsystem.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Coverage information for the core subsystem.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+return new class extends phpunit_coverage_info {
+    /** @var array The list of folders relative to the plugin root to whitelist in coverage generation. */
+    protected $whitelistfolders = [
+        'classes',
+
+        // This is a legacy hangup which relates to parts of the file storage API being placed in the wrong location.
+        'filestorage',
+    ];
+
+    /** @var array The list of files relative to the plugin root to whitelist in coverage generation. */
+    protected $whitelistfiles = [];
+
+    /** @var array The list of folders relative to the plugin root to excludelist in coverage generation. */
+    protected $excludelistfolders = [
+        'filestorage/tests',
+    ];
+
+    /** @var array The list of files relative to the plugin root to excludelist in coverage generation. */
+    protected $excludelistfiles = [];
+};
index 075bd95..4c262dd 100644 (file)
 <!--@plugin_suites_end@-->
 
     </testsuites>
+    <filter>
+<!--@filterlist@-->
+    </filter>
 
 </phpunit>
diff --git a/privacy/tests/coverage.php b/privacy/tests/coverage.php
new file mode 100644 (file)
index 0000000..9af4b87
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Coverage information for the tool_dataprivacy plugin.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Coverage information for the tool_dataprivacy plugin.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+return new class extends phpunit_coverage_info {
+    /** @var array The list of folders relative to the plugin root to whitelist in coverage generation. */
+    protected $whitelistfolders = [
+        'classes',
+    ];
+
+    /** @var array The list of files relative to the plugin root to whitelist in coverage generation. */
+    protected $whitelistfiles = [];
+
+    /** @var array The list of folders relative to the plugin root to excludelist in coverage generation. */
+    protected $excludelistfolders = [
+    ];
+
+    /** @var array The list of files relative to the plugin root to excludelist in coverage generation. */
+    protected $excludelistfiles = [];
+};