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