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