1 #!/usr/bin/env python 2 # Copyright 2013 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 import unittest 7 8 from app_yaml_helper import AppYamlHelper 9 from content_providers import IgnoreMissingContentProviders 10 from cron_servlet import CronServlet 11 from empty_dir_file_system import EmptyDirFileSystem 12 from environment import GetAppVersion 13 from extensions_paths import ( 14 APP_YAML, CONTENT_PROVIDERS, CHROME_EXTENSIONS, PUBLIC_TEMPLATES, SERVER2, 15 STATIC_DOCS) 16 from gcs_file_system_provider import CloudStorageFileSystemProvider 17 from github_file_system_provider import GithubFileSystemProvider 18 from host_file_system_provider import HostFileSystemProvider 19 from local_file_system import LocalFileSystem 20 from mock_file_system import MockFileSystem 21 from servlet import Request 22 from test_branch_utility import TestBranchUtility 23 from test_file_system import MoveTo, TestFileSystem 24 from test_util import EnableLogging, ReadFile 25 26 27 # NOTE(kalman): The ObjectStore created by the CronServlet is backed onto our 28 # fake AppEngine memcache/datastore, so the tests aren't isolated. Of course, 29 # if the host file systems have different identities, they will be, sort of. 30 class _TestDelegate(CronServlet.Delegate): 31 def __init__(self, create_file_system): 32 self.file_systems = [] 33 # A callback taking a revision and returning a file system. 34 self._create_file_system = create_file_system 35 self._app_version = GetAppVersion() 36 37 def CreateBranchUtility(self, object_store_creator): 38 return TestBranchUtility.CreateWithCannedData() 39 40 def CreateHostFileSystemProvider(self, 41 object_store_creator, 42 max_trunk_revision=None): 43 def constructor(branch=None, revision=None): 44 file_system = self._create_file_system(revision) 45 self.file_systems.append(file_system) 46 return file_system 47 return HostFileSystemProvider(object_store_creator, 48 max_trunk_revision=max_trunk_revision, 49 constructor_for_test=constructor) 50 51 def CreateGithubFileSystemProvider(self, object_store_creator): 52 return GithubFileSystemProvider.ForEmpty() 53 54 def CreateGCSFileSystemProvider(self, object_store_creator): 55 return CloudStorageFileSystemProvider.ForEmpty() 56 57 def GetAppVersion(self): 58 return self._app_version 59 60 # (non-Delegate method). 61 def SetAppVersion(self, app_version): 62 self._app_version = app_version 63 64 class CronServletTest(unittest.TestCase): 65 @EnableLogging('info') 66 def testEverything(self): 67 # All these tests are dependent (see above comment) so lump everything in 68 # the one test. 69 delegate = _TestDelegate(lambda _: MockFileSystem(LocalFileSystem.Create())) 70 71 # Test that the cron runs successfully. 72 response = CronServlet(Request.ForTest('trunk'), 73 delegate_for_test=delegate).Get() 74 self.assertEqual(200, response.status) 75 76 # Save the file systems created, start with a fresh set for the next run. 77 first_run_file_systems = delegate.file_systems[:] 78 delegate.file_systems[:] = [] 79 80 # When re-running, all file systems should be Stat()d the same number of 81 # times, but the second round shouldn't have been re-Read() since the 82 # Stats haven't changed. 83 response = CronServlet(Request.ForTest('trunk'), 84 delegate_for_test=delegate).Get() 85 self.assertEqual(200, response.status) 86 87 self.assertEqual(len(first_run_file_systems), len(delegate.file_systems)) 88 for i, second_run_file_system in enumerate(delegate.file_systems): 89 self.assertTrue(*second_run_file_system.CheckAndReset( 90 read_count=0, 91 stat_count=first_run_file_systems[i].GetStatCount())) 92 93 @IgnoreMissingContentProviders 94 def testSafeRevision(self): 95 test_data = { 96 'api': { 97 '_api_features.json': '{}', 98 '_manifest_features.json': '{}', 99 '_permission_features.json': '{}', 100 }, 101 'docs': { 102 'examples': { 103 'examples.txt': 'examples.txt contents' 104 }, 105 'server2': { 106 'app.yaml': AppYamlHelper.GenerateAppYaml('2-0-8') 107 }, 108 'static': { 109 'static.txt': 'static.txt contents' 110 }, 111 'templates': { 112 'articles': { 113 'activeTab.html': 'activeTab.html contents' 114 }, 115 'intros': { 116 'browserAction.html': 'activeTab.html contents' 117 }, 118 'private': { 119 'table_of_contents.html': 'table_of_contents.html contents', 120 }, 121 'public': { 122 'apps': { 123 'storage.html': '<h1>storage.html</h1> contents' 124 }, 125 'extensions': { 126 'storage.html': '<h1>storage.html</h1> contents' 127 }, 128 }, 129 'json': { 130 'chrome_sidenav.json': '{}', 131 'content_providers.json': ReadFile(CONTENT_PROVIDERS), 132 'manifest.json': '{}', 133 'permissions.json': '{}', 134 'strings.json': '{}', 135 'whats_new.json': '{}', 136 }, 137 } 138 } 139 } 140 141 updates = [] 142 143 def app_yaml_update(version): 144 return MoveTo(SERVER2, { 145 'app.yaml': AppYamlHelper.GenerateAppYaml(version) 146 }) 147 def storage_html_update(update): 148 return MoveTo(PUBLIC_TEMPLATES, { 149 'apps': {'storage.html': update} 150 }) 151 def static_txt_update(update): 152 return MoveTo(STATIC_DOCS, { 153 'static.txt': update 154 }) 155 156 storage_html_path = PUBLIC_TEMPLATES + 'apps/storage.html' 157 static_txt_path = STATIC_DOCS + 'static.txt' 158 159 def create_file_system(revision=None): 160 '''Creates a MockFileSystem at |revision| by applying that many |updates| 161 to it. 162 ''' 163 mock_file_system = MockFileSystem( 164 TestFileSystem(test_data, relative_to=CHROME_EXTENSIONS)) 165 updates_for_revision = ( 166 updates if revision is None else updates[:int(revision)]) 167 for update in updates_for_revision: 168 mock_file_system.Update(update) 169 return mock_file_system 170 171 delegate = _TestDelegate(create_file_system) 172 delegate.SetAppVersion('2-0-8') 173 174 file_systems = delegate.file_systems 175 176 # No updates applied yet. 177 CronServlet(Request.ForTest('trunk'), delegate_for_test=delegate).Get() 178 self.assertEqual(AppYamlHelper.GenerateAppYaml('2-0-8'), 179 file_systems[-1].ReadSingle(APP_YAML).Get()) 180 self.assertEqual('<h1>storage.html</h1> contents', 181 file_systems[-1].ReadSingle(storage_html_path).Get()) 182 183 # Apply updates to storage.html. 184 updates.append(storage_html_update('interim contents')) 185 updates.append(storage_html_update('<h1>new</h1> contents')) 186 187 CronServlet(Request.ForTest('trunk'), delegate_for_test=delegate).Get() 188 self.assertEqual(AppYamlHelper.GenerateAppYaml('2-0-8'), 189 file_systems[-1].ReadSingle(APP_YAML).Get()) 190 self.assertEqual('<h1>new</h1> contents', 191 file_systems[-1].ReadSingle(storage_html_path).Get()) 192 193 # Apply several updates to storage.html and app.yaml. The file system 194 # should be pinned at the version before app.yaml changed. 195 updates.append(storage_html_update('<h1>stuck here</h1> contents')) 196 197 double_update = storage_html_update('<h1>newer</h1> contents') 198 double_update.update(app_yaml_update('2-0-10')) 199 updates.append(double_update) 200 201 updates.append(storage_html_update('never gonna reach here')) 202 203 CronServlet(Request.ForTest('trunk'), delegate_for_test=delegate).Get() 204 self.assertEqual(AppYamlHelper.GenerateAppYaml('2-0-8'), 205 file_systems[-1].ReadSingle(APP_YAML).Get()) 206 self.assertEqual('<h1>stuck here</h1> contents', 207 file_systems[-1].ReadSingle(storage_html_path).Get()) 208 209 # Further pushes to storage.html will keep it pinned. 210 updates.append(storage_html_update('<h1>y</h1> u not update!')) 211 212 CronServlet(Request.ForTest('trunk'), delegate_for_test=delegate).Get() 213 self.assertEqual(AppYamlHelper.GenerateAppYaml('2-0-8'), 214 file_systems[-1].ReadSingle(APP_YAML).Get()) 215 self.assertEqual('<h1>stuck here</h1> contents', 216 file_systems[-1].ReadSingle(storage_html_path).Get()) 217 218 # Likewise app.yaml. 219 updates.append(app_yaml_update('2-1-0')) 220 221 CronServlet(Request.ForTest('trunk'), delegate_for_test=delegate).Get() 222 self.assertEqual(AppYamlHelper.GenerateAppYaml('2-0-8'), 223 file_systems[-1].ReadSingle(APP_YAML).Get()) 224 self.assertEqual('<h1>stuck here</h1> contents', 225 file_systems[-1].ReadSingle(storage_html_path).Get()) 226 227 # And updates to other content won't happen either. 228 updates.append(static_txt_update('important content!')) 229 230 CronServlet(Request.ForTest('trunk'), delegate_for_test=delegate).Get() 231 self.assertEqual(AppYamlHelper.GenerateAppYaml('2-0-8'), 232 file_systems[-1].ReadSingle(APP_YAML).Get()) 233 self.assertEqual('<h1>stuck here</h1> contents', 234 file_systems[-1].ReadSingle(storage_html_path).Get()) 235 self.assertEqual('static.txt contents', 236 file_systems[-1].ReadSingle(static_txt_path).Get()) 237 238 # Lastly - when the app version changes, everything should no longer be 239 # pinned. 240 delegate.SetAppVersion('2-1-0') 241 CronServlet(Request.ForTest('trunk'), delegate_for_test=delegate).Get() 242 self.assertEqual(AppYamlHelper.GenerateAppYaml('2-1-0'), 243 file_systems[-1].ReadSingle(APP_YAML).Get()) 244 self.assertEqual('<h1>y</h1> u not update!', 245 file_systems[-1].ReadSingle(storage_html_path).Get()) 246 self.assertEqual('important content!', 247 file_systems[-1].ReadSingle(static_txt_path).Get()) 248 249 250 if __name__ == '__main__': 251 unittest.main() 252