]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
application: refactor version checks, move to ApplicationUtils 390/head
authorVirtualTam <virtualtam@flibidi.net>
Tue, 24 Nov 2015 01:52:22 +0000 (02:52 +0100)
committerVirtualTam <virtualtam@flibidi.net>
Thu, 26 Nov 2015 22:19:37 +0000 (23:19 +0100)
Relates to #372

Modifications:
 - move checkUpdate() to ApplicationUtils
 - reduce file I/O operations during version checks
 - apply coding conventions
 - add test coverage

Tools:
 - create a sandbox directory for tests

Signed-off-by: VirtualTam <virtualtam@flibidi.net>
.gitignore
Makefile
application/ApplicationUtils.php
index.php
tests/ApplicationUtilsTest.php
tests/CacheTest.php
tests/CachedPageTest.php
tests/LinkDBTest.php

index b98c38b9bfd68374354842bf3a62cb941fc69743..75cd3a6b842a264e9b2423c8391a7ddaa090fc9a 100644 (file)
@@ -19,9 +19,8 @@ composer.lock
 # Ignore development and test resources
 coverage
 doxygen
-tests/datastore.php
-tests/dummycache/
+sandbox
 phpmd.html
 
 # Ignore user plugin configuration
-plugins/*/config.php
\ No newline at end of file
+plugins/*/config.php
index c560d8d16d60f7e023d06c4ec66eaa483b0631f9..a86f9aa8bbe8bdb0b160db7cea331ba605a3eeb0 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -110,6 +110,7 @@ test:
        @echo "-------"
        @echo "PHPUNIT"
        @echo "-------"
+       @mkdir -p sandbox
        @$(BIN)/phpunit tests
 
 ##
@@ -119,6 +120,7 @@ test:
 ### remove all unversioned files
 clean:
        @git clean -df
+       @rm -rf sandbox
 
 ### generate Doxygen documentation
 doxygen: clean
index b0e94e24429cedbbd1645773e8d5e5525b5d6ae7..c7414b776e56fdef23e5aef76f358566e7d50e21 100644 (file)
@@ -4,6 +4,98 @@
  */
 class ApplicationUtils
 {
+    private static $GIT_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli';
+    private static $GIT_BRANCH = 'master';
+    private static $VERSION_FILE = 'shaarli_version.php';
+    private static $VERSION_START_TAG = '<?php /* ';
+    private static $VERSION_END_TAG = ' */ ?>';
+
+    /**
+     * Gets the latest version code from the Git repository
+     *
+     * The code is read from the raw content of the version file on the Git server.
+     *
+     * @return mixed the version code from the repository if available, else 'false'
+     */
+    public static function getLatestGitVersionCode($url, $timeout=2)
+    {
+        list($headers, $data) = get_http_url($url, $timeout);
+
+        if (strpos($headers[0], '200 OK') === false) {
+            error_log('Failed to retrieve ' . $url);
+            return false;
+        }
+
+        return str_replace(
+            array(self::$VERSION_START_TAG, self::$VERSION_END_TAG, PHP_EOL),
+            array('', '', ''),
+            $data
+        );
+    }
+
+    /**
+     * Checks if a new Shaarli version has been published on the Git repository
+     *
+     * Updates checks are run periodically, according to the following criteria:
+     * - the update checks are enabled (install, global config);
+     * - the user is logged in (or this is an open instance);
+     * - the last check is older than a given interval;
+     * - the check is non-blocking if the HTTPS connection to Git fails;
+     * - in case of failure, the update file's modification date is updated,
+     *   to avoid intempestive connection attempts.
+     *
+     * @param string $currentVersion the current version code
+     * @param string $updateFile     the file where to store the latest version code
+     * @param int    $checkInterval  the minimum interval between update checks (in seconds
+     * @param bool   $enableCheck    whether to check for new versions
+     * @param bool   $isLoggedIn     whether the user is logged in
+     *
+     * @return mixed the new version code if available and greater, else 'false'
+     */
+    public static function checkUpdate(
+        $currentVersion, $updateFile, $checkInterval, $enableCheck, $isLoggedIn)
+    {
+        if (! $isLoggedIn) {
+            // Do not check versions for visitors
+            return false;
+        }
+
+        if (empty($enableCheck)) {
+            // Do not check if the user doesn't want to
+            return false;
+        }
+
+        if (is_file($updateFile) && (filemtime($updateFile) > time() - $checkInterval)) {
+            // Shaarli has checked for updates recently - skip HTTP query
+            $latestKnownVersion = file_get_contents($updateFile);
+
+            if (version_compare($latestKnownVersion, $currentVersion) == 1) {
+                return $latestKnownVersion;
+            }
+            return false;
+        }
+
+        // Late Static Binding allows overriding within tests
+        // See http://php.net/manual/en/language.oop5.late-static-bindings.php
+        $latestVersion = static::getLatestGitVersionCode(
+            self::$GIT_URL . '/' . self::$GIT_BRANCH . '/' . self::$VERSION_FILE
+        );
+
+        if (! $latestVersion) {
+            // Only update the file's modification date
+            file_put_contents($updateFile, $currentVersion);
+            return false;
+        }
+
+        // Update the file's content and modification date
+        file_put_contents($updateFile, $latestVersion);
+
+        if (version_compare($latestVersion, $currentVersion) == 1) {
+            return $latestVersion;
+        }
+
+        return false;
+    }
 
     /**
      * Checks the PHP version to ensure Shaarli can run
index 3954be973fa6db33a06f930d775b1098f7f5bce2..90045d607437d73d07b029b47a64f2aa4b35b6f5 100644 (file)
--- a/index.php
+++ b/index.php
@@ -305,32 +305,6 @@ function setup_login_state() {
 }
 $userIsLoggedIn = setup_login_state();
 
-// Checks if an update is available for Shaarli.
-// (at most once a day, and only for registered user.)
-// Output: '' = no new version.
-//         other= the available version.
-function checkUpdate()
-{
-    if (!isLoggedIn()) return ''; // Do not check versions for visitors.
-    if (empty($GLOBALS['config']['ENABLE_UPDATECHECK'])) return ''; // Do not check if the user doesn't want to.
-
-    // Get latest version number at most once a day.
-    if (!is_file($GLOBALS['config']['UPDATECHECK_FILENAME']) || (filemtime($GLOBALS['config']['UPDATECHECK_FILENAME'])<time()-($GLOBALS['config']['UPDATECHECK_INTERVAL'])))
-    {
-        $version = shaarli_version;
-        list($headers, $data) = get_http_url('https://raw.githubusercontent.com/shaarli/Shaarli/master/shaarli_version.php', 2);
-        if (strpos($headers[0], '200 OK') !== false) {
-            $version = str_replace(' */ ?>', '', str_replace('<?php /* ', '', $data));
-        }
-        // If failed, never mind. We don't want to bother the user with that.
-        file_put_contents($GLOBALS['config']['UPDATECHECK_FILENAME'],$version); // touch file date
-    }
-    // Compare versions:
-    $newestversion=file_get_contents($GLOBALS['config']['UPDATECHECK_FILENAME']);
-    if (version_compare($newestversion,shaarli_version)==1) return $newestversion;
-    return '';
-}
-
 
 // -----------------------------------------------------------------------------------------------
 // Log to text file
@@ -657,7 +631,18 @@ class pageBuilder
     private function initialize()
     {
         $this->tpl = new RainTPL;
-        $this->tpl->assign('newversion', escape(checkUpdate()));
+        $this->tpl->assign(
+            'newversion',
+            escape(
+                ApplicationUtils::checkUpdate(
+                    shaarli_version,
+                    $GLOBALS['config']['UPDATECHECK_FILENAME'],
+                    $GLOBALS['config']['UPDATECHECK_INTERVAL'],
+                    $GLOBALS['config']['ENABLE_UPDATECHECK'],
+                    isLoggedIn()
+                )
+            )
+        );
         $this->tpl->assign('feedurl', escape(index_url($_SERVER)));
         $searchcrits = ''; // Search criteria
         if (!empty($_GET['searchtags'])) {
index 01301e689dec6bc47b12dc59aa68b585cd7cd860..437c21fd14ba96fbffaed6c4157c66f4875075f0 100644 (file)
 
 require_once 'application/ApplicationUtils.php';
 
+/**
+ * Fake ApplicationUtils class to avoid HTTP requests
+ */
+class FakeApplicationUtils extends ApplicationUtils
+{
+    public static $VERSION_CODE = '';
+
+    /**
+     * Toggle HTTP requests, allow overriding the version code
+     */
+    public static function getLatestGitVersionCode($url, $timeout=0)
+    {
+        return self::$VERSION_CODE;
+    }
+}
+
 
 /**
  * Unitary tests for Shaarli utilities
  */
 class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
 {
+    protected static $testUpdateFile = 'sandbox/update.txt';
+    protected static $testVersion = '0.5.0';
+    protected static $versionPattern = '/^\d+\.\d+\.\d+$/';
+
+    /**
+     * Reset test data for each test
+     */
+    public function setUp()
+    {
+        FakeApplicationUtils::$VERSION_CODE = '';
+        if (file_exists(self::$testUpdateFile)) {
+            unlink(self::$testUpdateFile);
+        }
+    }
+
+    /**
+     * Retrieve the latest version code available on Git
+     *
+     * Expected format: Semantic Versioning - major.minor.patch
+     */
+    public function testGetLatestGitVersionCode()
+    {
+        $testTimeout = 10;
+
+        $this->assertEquals(
+            '0.5.4',
+            ApplicationUtils::getLatestGitVersionCode(
+                'https://raw.githubusercontent.com/shaarli/Shaarli/'
+               .'v0.5.4/shaarli_version.php',
+                $testTimeout
+            )
+        );
+        $this->assertRegexp(
+            self::$versionPattern,
+            ApplicationUtils::getLatestGitVersionCode(
+                'https://raw.githubusercontent.com/shaarli/Shaarli/'
+               .'master/shaarli_version.php',
+                $testTimeout
+            )
+        );
+    }
+
+    /**
+     * Attempt to retrieve the latest version from an invalid URL
+     */
+    public function testGetLatestGitVersionCodeInvalidUrl()
+    {
+        $this->assertFalse(
+            ApplicationUtils::getLatestGitVersionCode('htttp://null.io', 0)
+        );
+    }
+
+    /**
+     * Test update checks - the user is logged off
+     */
+    public function testCheckUpdateLoggedOff()
+    {
+        $this->assertFalse(
+            ApplicationUtils::checkUpdate(self::$testVersion, 'null', 0, false, false)
+        );
+    }
+
+    /**
+     * Test update checks - the user has disabled updates
+     */
+    public function testCheckUpdateUserDisabled()
+    {
+        $this->assertFalse(
+            ApplicationUtils::checkUpdate(self::$testVersion, 'null', 0, false, true)
+        );
+    }
+
+    /**
+     * A newer version is available
+     */
+    public function testCheckUpdateNewVersionNew()
+    {
+        $newVersion = '1.8.3';
+        FakeApplicationUtils::$VERSION_CODE = $newVersion;
+
+        $version = FakeApplicationUtils::checkUpdate(
+            self::$testVersion,
+            self::$testUpdateFile,
+            100,
+            true,
+            true
+        );
+
+        $this->assertEquals($newVersion, $version);
+    }
+
+    /**
+     * No available information about versions
+     */
+    public function testCheckUpdateNewVersionUnavailable()
+    {
+        $version = FakeApplicationUtils::checkUpdate(
+            self::$testVersion,
+            self::$testUpdateFile,
+            100,
+            true,
+            true
+        );
+
+        $this->assertFalse($version);
+    }
+
+    /**
+     * Shaarli is up-to-date
+     */
+    public function testCheckUpdateNewVersionUpToDate()
+    {
+        FakeApplicationUtils::$VERSION_CODE = self::$testVersion;
+
+        $version = FakeApplicationUtils::checkUpdate(
+            self::$testVersion,
+            self::$testUpdateFile,
+            100,
+            true,
+            true
+        );
+
+        $this->assertFalse($version);
+    }
+
+    /**
+     * Time-traveller's Shaarli
+     */
+    public function testCheckUpdateNewVersionMaartiMcFly()
+    {
+        FakeApplicationUtils::$VERSION_CODE = '0.4.1';
+
+        $version = FakeApplicationUtils::checkUpdate(
+            self::$testVersion,
+            self::$testUpdateFile,
+            100,
+            true,
+            true
+        );
+
+        $this->assertFalse($version);
+    }
+
+    /**
+     * The version has been checked recently and Shaarli is up-to-date
+     */
+    public function testCheckUpdateNewVersionTwiceUpToDate()
+    {
+        FakeApplicationUtils::$VERSION_CODE = self::$testVersion;
+
+        // Create the update file
+        $version = FakeApplicationUtils::checkUpdate(
+            self::$testVersion,
+            self::$testUpdateFile,
+            100,
+            true,
+            true
+        );
+
+        $this->assertFalse($version);
+
+        // Reuse the update file
+        $version = FakeApplicationUtils::checkUpdate(
+            self::$testVersion,
+            self::$testUpdateFile,
+            100,
+            true,
+            true
+        );
+
+        $this->assertFalse($version);
+    }
+
+    /**
+     * The version has been checked recently and Shaarli is outdated
+     */
+    public function testCheckUpdateNewVersionTwiceOutdated()
+    {
+        $newVersion = '1.8.3';
+        FakeApplicationUtils::$VERSION_CODE = $newVersion;
+
+        // Create the update file
+        $version = FakeApplicationUtils::checkUpdate(
+            self::$testVersion,
+            self::$testUpdateFile,
+            100,
+            true,
+            true
+        );
+        $this->assertEquals($newVersion, $version);
+
+        // Reuse the update file
+        $version = FakeApplicationUtils::checkUpdate(
+            self::$testVersion,
+            self::$testUpdateFile,
+            100,
+            true,
+            true
+        );
+        $this->assertEquals($newVersion, $version);
+    }
+
     /**
      * Check supported PHP versions
      */
index aa5395b0ab8f16b1906dfa8ece3b1976a43d9d94..eacd4487dd73f908a17ea999deaffc86663f44f0 100644 (file)
@@ -11,10 +11,10 @@ require_once 'application/Cache.php';
 /**
  * Unitary tests for cached pages
  */
-class CachedTest extends PHPUnit_Framework_TestCase
+class CacheTest extends PHPUnit_Framework_TestCase
 {
     // test cache directory
-    protected static $testCacheDir = 'tests/dummycache';
+    protected static $testCacheDir = 'sandbox/dummycache';
 
     // dummy cached file names / content
     protected static $pages = array('a', 'toto', 'd7b59c');
@@ -56,7 +56,7 @@ class CachedTest extends PHPUnit_Framework_TestCase
     public function testPurgeCachedPagesMissingDir()
     {
         $this->assertEquals(
-            'Cannot purge tests/dummycache_missing: no directory',
+            'Cannot purge sandbox/dummycache_missing: no directory',
             purgeCachedPages(self::$testCacheDir.'_missing')
         );
     }
index e97af030af77f4619f41947987caeddbdf30b8b7..51565cd63f591c5e69ff41a1eb29d1e616b52cba 100644 (file)
@@ -11,7 +11,7 @@ require_once 'application/CachedPage.php';
 class CachedPageTest extends PHPUnit_Framework_TestCase
 {
     // test cache directory
-    protected static $testCacheDir = 'tests/pagecache';
+    protected static $testCacheDir = 'sandbox/pagecache';
     protected static $url = 'http://shaar.li/?do=atom';
     protected static $filename;
 
index ff917f6d54dba81c6c736352b6b7e56043b67e1c..7b22b2704f8ba84f3e26995f36c84d071b71161d 100644 (file)
@@ -16,7 +16,7 @@ require_once 'tests/utils/ReferenceLinkDB.php';
 class LinkDBTest extends PHPUnit_Framework_TestCase
 {
     // datastore to test write operations
-    protected static $testDatastore = 'tests/datastore.php';
+    protected static $testDatastore = 'sandbox/datastore.php';
     protected static $refDB = null;
     protected static $publicLinkDB = null;
     protected static $privateLinkDB = null;