tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_caches_delete_cleanup_after_shutdown.py (8044B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 import os
      6 import time
      7 
      8 from marionette_driver import Wait
      9 from marionette_harness import MarionetteTestCase
     10 
     11 """
     12 Currently we expect our size after cleanup to be 98k for the current
     13 database schema and page sizes and constants used in this test. We
     14 set the threshold at 128k so the test doesn't start failing if these
     15 control objects/structures increase somewhat in size, but should still
     16 fail if we fail to delete all of the 5,000 1k files we create on disk
     17 as part of the test.
     18 """
     19 QM_TESTING_PREF = "dom.quotaManager.testing"
     20 
     21 EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP = 128 * 1024  # 128KB
     22 CACHE_ID = "data"
     23 
     24 
     25 class CachesDeleteCleanupAtShutdownTestCase(MarionetteTestCase):
     26    """
     27    Bug1784700: This test ensures that cache body files get cleaned up
     28    properly after cache has been deleted. Note that body files gets
     29    cleaned up asynchronously which means that these files might still
     30    be around even after Caches.Delete promise gets resolved. Currently,
     31    we would only clean up body files on origin initialization and that's
     32    a firefox is necessary here.
     33    """
     34 
     35    def setUp(self):
     36        super().setUp()
     37        self.marionette.restart(in_app=False, clean=True)
     38        self.marionette.set_pref(QM_TESTING_PREF, True)
     39 
     40    def tearDown(self):
     41        self.marionette.restart(in_app=False, clean=True)
     42        super().tearDown()
     43        self.marionette.set_pref(QM_TESTING_PREF, False)
     44 
     45    def getUsage(self):
     46        usage = self.marionette.execute_async_script(
     47            """
     48                const [resolve] = arguments;
     49                window.wrappedJSObject.getStorageEstimate()
     50                    .then(resolve)
     51                    .catch(()=>resolve(-1));
     52            """,
     53            new_sandbox=False,
     54        )
     55        assert (
     56            usage != -1
     57        )  # usage should not be an invalid number which gets sets in catch handler above.
     58        return usage
     59 
     60    def doCacheWork(self, n):
     61        # max timeout for this script to execute is 5 minutes
     62        maxTimeout = 5 * 60 * 1000
     63 
     64        assert self.marionette.execute_async_script(
     65            """
     66                const [cacheId, n, resolve] = arguments;
     67                window.wrappedJSObject.doCacheWork(cacheId, n)
     68                    .then(()=>resolve(true))
     69                    .catch(()=>resolve(false));
     70            """,
     71            script_args=(
     72                CACHE_ID,
     73                n,
     74            ),
     75            new_sandbox=False,
     76            script_timeout=maxTimeout,
     77        )
     78 
     79    def openCache(self):
     80        assert self.marionette.execute_async_script(
     81            """
     82                const [cacheId, resolve] = arguments;
     83                window.wrappedJSObject.openCache(cacheId)
     84                    .then(()=>resolve(true))
     85                    .catch(()=>resolve(false));
     86            """,
     87            new_sandbox=False,
     88            script_args=(CACHE_ID,),
     89        )
     90 
     91    # asynchronously iterating over body files could be challenging as firefox
     92    # might be doing some cleanup while we are iterating over it's morgue dir.
     93    # It's expected for this helper to throw FileNotFoundError exception in
     94    # such cases.
     95    def fallibleCountBodies(self, morgueDir):
     96        bodyCount = 0
     97        for elem in os.listdir(morgueDir):
     98            absPathElem = os.path.join(morgueDir, elem)
     99 
    100            if os.path.isdir(absPathElem):
    101                bodyCount += sum(
    102                    1
    103                    for e in os.listdir(absPathElem)
    104                    if os.path.isfile(os.path.join(absPathElem, e))
    105                )
    106        return bodyCount
    107 
    108    def countBodies(self):
    109        profile = self.marionette.instance.profile.profile
    110        originDir = (
    111            self.marionette.absolute_url("")[:-1].replace(":", "+").replace("/", "+")
    112        )
    113 
    114        morgueDir = f"{profile}/storage/default/{originDir}/cache/morgue"
    115        print("morgueDir path = ", morgueDir)
    116 
    117        if not os.path.exists(morgueDir):
    118            print("morgue directory does not exists.")
    119            return -1
    120 
    121        while True:
    122            try:
    123                return self.fallibleCountBodies(morgueDir)
    124            except FileNotFoundError:
    125                # we probably just got in the period when firefox was cleaning up.
    126                # retry...
    127                time.sleep(0.5)
    128 
    129    def ensureCleanDirectory(self):
    130        orphanedBodiesCount = self.countBodies()
    131        return orphanedBodiesCount <= 0
    132 
    133    def isStorageInitialized(self, temporary=False):
    134        with self.marionette.using_context("chrome"):
    135            return self.marionette.execute_async_script(
    136                """
    137                    const [resolve] = arguments;
    138                    const req = %s;
    139                    req.callback = () => {
    140                        resolve(req.resultCode == Cr.NS_OK && req.result)
    141                    };
    142                """
    143                % (
    144                    "Services.qms.temporaryStorageInitialized()"
    145                    if temporary
    146                    else "Services.qms.storageInitialized()"
    147                ),
    148                new_sandbox=False,
    149            )
    150 
    151    def create_and_cleanup_cache(self, ensureCleanCallback, in_app):
    152        # create 640 cache entries
    153        self.doCacheWork(640)
    154        print("usage after doCacheWork = ", self.getUsage())
    155 
    156        self.marionette.restart(in_app=in_app)
    157        print("restart successful")
    158 
    159        self.marionette.navigate(
    160            self.marionette.absolute_url("dom/cache/cacheUsage.html")
    161        )
    162        return ensureCleanCallback()
    163 
    164    def afterCleanupClosure(self, usage):
    165        print(
    166            f"Storage initialized = {self.isStorageInitialized()}, temporary storage initialized = {self.isStorageInitialized(True)}"
    167        )
    168 
    169        print(f"Usage = {usage} and number of orphaned bodies = {self.countBodies()}")
    170        return usage < EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP
    171 
    172    def test_ensure_cache_cleanup_after_clean_restart(self):
    173        self.marionette.navigate(
    174            self.marionette.absolute_url("dom/cache/cacheUsage.html")
    175        )
    176        beforeUsage = self.getUsage()
    177 
    178        def ensureCleanCallback():
    179            Wait(self.marionette, interval=1, timeout=60).until(
    180                lambda _: self.afterCleanupClosure(self.getUsage() - beforeUsage),
    181                message="Cache directory is not cleaned up properly",
    182            )
    183 
    184            return (
    185                abs(beforeUsage - self.getUsage())
    186                <= EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP
    187                and self.ensureCleanDirectory()
    188            )
    189 
    190        if not self.create_and_cleanup_cache(ensureCleanCallback, True):
    191            print(f"beforeUsage = {beforeUsage}, and afterUsage = {self.getUsage()}")
    192            assert False
    193 
    194    def test_ensure_cache_cleanup_after_unclean_restart(self):
    195        self.marionette.navigate(
    196            self.marionette.absolute_url("dom/cache/cacheUsage.html")
    197        )
    198 
    199        print(f"profile path = {self.marionette.instance.profile.profile}")
    200 
    201        beforeUsage = self.getUsage()
    202 
    203        def ensureCleanCallback():
    204            print(
    205                f"ensureCleanCallback, profile path = {self.marionette.instance.profile.profile}"
    206            )
    207 
    208            self.openCache()
    209            Wait(self.marionette, interval=1, timeout=60).until(
    210                lambda _: self.afterCleanupClosure(self.getUsage() - beforeUsage),
    211                message="Cache directory is not cleaned up properly",
    212            )
    213 
    214            return (
    215                abs(beforeUsage - self.getUsage())
    216                <= EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP
    217                and self.ensureCleanDirectory()
    218            )
    219 
    220        if not self.create_and_cleanup_cache(ensureCleanCallback, False):
    221            print(f"beforeUsage = {beforeUsage}, and afterUsage = {self.getUsage()}")
    222            assert False