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