From 2d8405e99e4088627580d32140dcd8cef383b6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mudr=C3=A1k?= Date: Tue, 5 Mar 2019 13:47:14 +0100 Subject: [PATCH] MDL-64994 analytics: Add a simple semantic version check method This method is to be used for checking that a compatible version of the moodlemlbackend package is installed on the server. The package is expected to use the semantic versioning scheme (semver.org). --- lib/mlbackend/python/classes/processor.php | 35 ++++ lib/mlbackend/python/tests/processor_test.php | 183 ++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 lib/mlbackend/python/tests/processor_test.php diff --git a/lib/mlbackend/python/classes/processor.php b/lib/mlbackend/python/classes/processor.php index e74de28cef2..cda1173dcd8 100644 --- a/lib/mlbackend/python/classes/processor.php +++ b/lib/mlbackend/python/classes/processor.php @@ -395,4 +395,39 @@ class processor implements \core_analytics\classifier, \core_analytics\regresso // This is not ideal, but there is no read access to moodle filesystem files. return $file->copy_content_to_temp('core_analytics'); } + + /** + * Check that the given package version can be used and return the error status. + * + * When evaluating the version, we assume the sematic versioning scheme as described at + * https://semver.org/. + * + * @param string $actual The actual Python package version + * @param string $required The required version of the package + * @return int -1 = actual version is too low, 1 = actual version too high, 0 = actual version is ok + */ + public static function check_pip_package_version($actual, $required = self::REQUIRED_PIP_PACKAGE_VERSION) { + + if (empty($actual)) { + return -1; + } + + if (version_compare($actual, $required, '<')) { + return -1; + } + + $parts = explode('.', $required); + $requiredapiver = reset($parts); + + $parts = explode('.', $actual); + $actualapiver = reset($parts); + + if ($requiredapiver > 0 || $actualapiver > 1) { + if (version_compare($actual, $requiredapiver + 1, '>=')) { + return 1; + } + } + + return 0; + } } diff --git a/lib/mlbackend/python/tests/processor_test.php b/lib/mlbackend/python/tests/processor_test.php new file mode 100644 index 00000000000..e9ceb51b273 --- /dev/null +++ b/lib/mlbackend/python/tests/processor_test.php @@ -0,0 +1,183 @@ +. + +/** + * Provides the {@link mlbackend_python_processor_testcase} class. + * + * @package mlbackend_python + * @category test + * @copyright 2019 David Mudrák + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Unit tests for the {@link \mlbackend_python\processor} class. + * + * @copyright 2019 David Mudrák + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mlbackend_python_processor_testcase extends advanced_testcase { + + /** + * Test implementation of the {@link \mlbackend_python\processor::check_pip_package_version()} method. + * + * @dataProvider check_pip_package_versions + * @param string $actual A sample of the actual package version + * @param string $required A sample of the required package version + * @param int $result Expected value returned by the tested method + */ + public function test_check_pip_package_version($actual, $required, $result) { + $this->assertSame($result, \mlbackend_python\processor::check_pip_package_version($actual, $required)); + } + + /** + * Check that the {@link \mlbackend_python\processor::check_pip_package_version()} can be called with single argument. + */ + public function test_check_pip_package_version_default() { + + $this->assertSame(-1, \mlbackend_python\processor::check_pip_package_version('0.0.1')); + $this->assertSame(0, \mlbackend_python\processor::check_pip_package_version( + \mlbackend_python\processor::REQUIRED_PIP_PACKAGE_VERSION)); + } + + /** + * Provides data samples for the {@link self::test_check_pip_package_version()}. + * + * @return array + */ + public function check_pip_package_versions() { + return [ + // Exact match. + [ + '0.0.5', + '0.0.5', + 0, + ], + [ + '1.0.0', + '1.0.0', + 0, + ], + // Actual version higher than required, yet still API compatible. + [ + '1.0.3', + '1.0.1', + 0, + ], + [ + '2.1.3', + '2.0.0', + 0, + ], + [ + '1.1.5', + '1.1', + 0, + ], + [ + '2.0.3', + '2', + 0, + ], + // Actual version not high enough to meet the requirements. + [ + '0.0.5', + '1.0.0', + -1, + ], + [ + '0.37.0', + '1.0.0', + -1, + ], + [ + '0.0.5', + '0.37.0', + -1, + ], + [ + '2.0.0', + '2.0.2', + -1, + ], + [ + '2.7.0', + '3.0', + -1, + ], + [ + '2.8.9-beta1', + '3.0', + -1, + ], + [ + '1.1.0-rc1', + '1.1.0', + -1, + ], + // Actual version too high and no longer API compatible. + [ + '2.0.0', + '1.0.0', + 1, + ], + [ + '3.1.5', + '2.0', + 1, + ], + [ + '3.0.0', + '1.0', + 1, + ], + [ + '2.0.0', + '0.0.5', + 1, + ], + [ + '3.0.2', + '0.37.0', + 1, + ], + // Zero major version requirement is fulfilled with 1.x API (0.x are not considered stable APIs). + [ + '1.0.0', + '0.0.5', + 0, + ], + [ + '1.8.6', + '0.37.0', + 0, + ], + // Empty version is never good enough. + [ + '', + '1.0.0', + -1, + ], + [ + '0.0.0', + '0.37.0', + -1, + ], + ]; + } +}