1#!/usr/bin/env python
2# Copyright (c) 2012 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 copy
7import datetime
8import hashlib
9import logging
10import os
11import posixpath
12import subprocess
13import sys
14import tempfile
15import unittest
16import urlparse
17
18SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
19BUILD_TOOLS_DIR = os.path.dirname(SCRIPT_DIR)
20
21sys.path.append(BUILD_TOOLS_DIR)
22import manifest_util
23import update_nacl_manifest
24from update_nacl_manifest import CANARY_BUNDLE_NAME
25
26
27HTTPS_BASE_URL = 'https://storage.googleapis.com' \
28    '/nativeclient_mirror/nacl/nacl_sdk/'
29
30OS_CR = ('cros',)
31OS_L = ('linux',)
32OS_M = ('mac',)
33OS_ML = ('mac', 'linux')
34OS_MW = ('mac', 'win')
35OS_LW = ('linux', 'win')
36OS_MLW = ('mac', 'linux', 'win')
37OS_ALL = ('all',)
38POST_STABLE = 'post_stable'
39STABLE = 'stable'
40BETA = 'beta'
41DEV = 'dev'
42CANARY = 'canary'
43
44
45def GetArchiveURL(basename, version):
46  return urlparse.urljoin(HTTPS_BASE_URL, posixpath.join(version, basename))
47
48
49def GetPlatformArchiveUrl(host_os, version):
50  basename = 'naclsdk_%s.tar.bz2' % (host_os,)
51  return GetArchiveURL(basename, version)
52
53
54def MakeGsUrl(rel_path):
55  return update_nacl_manifest.GS_BUCKET_PATH + rel_path
56
57
58def GetPathFromGsUrl(url):
59  assert url.startswith(update_nacl_manifest.GS_BUCKET_PATH)
60  return url[len(update_nacl_manifest.GS_BUCKET_PATH):]
61
62
63def GetPathFromHttpsUrl(url):
64  assert url.startswith(HTTPS_BASE_URL)
65  return url[len(HTTPS_BASE_URL):]
66
67
68def MakeArchive(url, host_os):
69  archive = manifest_util.Archive(host_os)
70  archive.url = url
71  # dummy values that won't succeed if we ever use them, but will pass
72  # validation. :)
73  archive.checksum = {'sha1': 'foobar'}
74  archive.size = 1
75  return archive
76
77
78def MakePlatformArchive(host_os, version):
79  return MakeArchive(GetPlatformArchiveUrl(host_os, version), host_os)
80
81
82def MakeNonPlatformArchive(basename, version):
83  return MakeArchive(GetArchiveURL(basename, version), 'all')
84
85
86def MakeNonPepperBundle(name, with_archives=False):
87  bundle = manifest_util.Bundle(name)
88  bundle.version = 1
89  bundle.revision = 1
90  bundle.description = 'Dummy bundle'
91  bundle.recommended = 'yes'
92  bundle.stability = 'stable'
93
94  if with_archives:
95    for host_os in OS_MLW:
96      archive = manifest_util.Archive(host_os)
97      archive.url = 'http://example.com'
98      archive.checksum = {'sha1': 'blah'}
99      archive.size = 2
100      bundle.AddArchive(archive)
101  return bundle
102
103
104def MakePepperBundle(major_version, revision=0, version=None, stability='dev',
105                     bundle_name=None):
106  assert (version is None or
107          version.split('.')[0] == 'trunk' or
108          version.split('.')[0] == str(major_version))
109  if not bundle_name:
110    bundle_name = 'pepper_' + str(major_version)
111
112  bundle = manifest_util.Bundle(bundle_name)
113  bundle.version = major_version
114  bundle.revision = revision
115  bundle.description = 'Chrome %s bundle, revision %s' % (major_version,
116                                                          revision)
117  bundle.repath = 'pepper_' + str(major_version)
118  bundle.recommended = 'no'
119  bundle.stability = stability
120
121  return bundle
122
123
124def MakePlatformBundle(major_version, revision=0, version=None, host_oses=None,
125                       stability='dev'):
126  bundle = MakePepperBundle(major_version, revision, version, stability)
127
128  if host_oses:
129    for host_os in host_oses:
130      bundle.AddArchive(MakePlatformArchive(host_os, version))
131
132  return bundle
133
134
135def MakeBionicBundle(major_version, revision=0, version=None, host_oses=None):
136  bundle = MakePepperBundle(major_version, revision, version, 'dev')
137
138  if host_oses:
139    for host_os in host_oses:
140      bundle.AddArchive(MakeBionicArchive(host_os, version))
141
142  return bundle
143
144
145class MakeManifest(manifest_util.SDKManifest):
146  def __init__(self, *args):
147    manifest_util.SDKManifest.__init__(self)
148
149    for bundle in args:
150      self.AddBundle(bundle)
151
152  def AddBundle(self, bundle):
153    self.MergeBundle(bundle, allow_existing=False)
154
155
156class MakeHistory(object):
157  def __init__(self):
158    # used for a dummy timestamp
159    self.datetime = datetime.datetime.utcnow()
160    self.history = []
161
162  def Add(self, host_oses, channel, version):
163    for host_os in host_oses:
164      timestamp = self.datetime.strftime('%Y-%m-%d %H:%M:%S.%f')
165      self.history.append((host_os, channel, version, timestamp))
166      self.datetime += datetime.timedelta(0, -3600) # one hour earlier
167    self.datetime += datetime.timedelta(-1) # one day earlier
168
169
170class MakeFiles(dict):
171  def AddOnlineManifest(self, manifest_string):
172    self['naclsdk_manifest2.json'] = manifest_string
173
174  def Add(self, bundle, add_archive_for_os=OS_MLW, add_json_for_os=OS_MLW):
175    for archive in bundle.GetArchives():
176      if not archive.host_os in add_archive_for_os:
177        continue
178
179      self.AddArchive(bundle, archive, archive.host_os in add_json_for_os)
180
181  def AddArchive(self, bundle, archive, add_json=True):
182    path = GetPathFromHttpsUrl(archive.url)
183    self[path] = 'My Dummy archive'
184
185    if add_json:
186      # add .json manifest snippet, it should look like a normal Bundle, but
187      # only has one archive.
188      new_bundle = manifest_util.Bundle('')
189      new_bundle.CopyFrom(bundle)
190      del new_bundle.archives[:]
191      new_bundle.AddArchive(archive)
192      self[path + '.json'] = new_bundle.GetDataAsString()
193
194
195class TestDelegate(update_nacl_manifest.Delegate):
196  def __init__(self, manifest, history, files):
197    self.manifest = manifest
198    self.history = history
199    self.files = files
200    self.dryrun = 0
201    self.called_gsutil_cp = False
202    self.called_sendmail = False
203
204  def GetRepoManifest(self):
205    return self.manifest
206
207  def GetHistory(self):
208    return self.history
209
210  def GsUtil_ls(self, url):
211    path = GetPathFromGsUrl(url)
212    result = []
213    for filename in self.files.iterkeys():
214      if not filename.startswith(path):
215        continue
216
217      # Find the first slash after the prefix (path).
218      # +1, because if the slash is directly after path, then we want to find
219      # the following slash anyway.
220      slash = filename.find('/', len(path) + 1)
221
222      if slash != -1:
223        filename = filename[:slash]
224
225      result.append(MakeGsUrl(filename))
226
227    # Remove dupes.
228    return list(set(result))
229
230  def GsUtil_cat(self, url):
231    path = GetPathFromGsUrl(url)
232    if path not in self.files:
233      raise subprocess.CalledProcessError(1, 'gsutil cat %s' % (url,))
234    return self.files[path]
235
236  def GsUtil_cp(self, src, dest, stdin=None):
237    self.called_gsutil_cp = True
238    dest_path = GetPathFromGsUrl(dest)
239    if src == '-':
240      self.files[dest_path] = stdin
241    else:
242      src_path = GetPathFromGsUrl(src)
243      if src_path not in self.files:
244        raise subprocess.CalledProcessError(1, 'gsutil cp %s %s' % (src, dest))
245      self.files[dest_path] = self.files[src_path]
246
247  def SendMail(self, subject, text):
248    self.called_sendmail = True
249
250
251# Shorthand for premade bundles/versions
252V18_0_1025_163 = '18.0.1025.163'
253V18_0_1025_175 = '18.0.1025.175'
254V18_0_1025_184 = '18.0.1025.184'
255V19_0_1084_41 = '19.0.1084.41'
256V19_0_1084_67 = '19.0.1084.67'
257V21_0_1145_0 = '21.0.1145.0'
258V21_0_1166_0 = '21.0.1166.0'
259V26_0_1386_0 = '26.0.1386.0'
260V26_0_1386_1 = '26.0.1386.1'
261V37_0_2054_0 = '37.0.2054.0'
262VTRUNK_140819 = 'trunk.140819'
263VTRUNK_277776 = 'trunk.277776'
264B18_0_1025_163_MLW = MakePlatformBundle(18, 132135, V18_0_1025_163, OS_MLW)
265B18_0_1025_184_MLW = MakePlatformBundle(18, 134900, V18_0_1025_184, OS_MLW)
266B18_NONE = MakePlatformBundle(18)
267B19_0_1084_41_MLW = MakePlatformBundle(19, 134854, V19_0_1084_41, OS_MLW)
268B19_0_1084_67_MLW = MakePlatformBundle(19, 142000, V19_0_1084_67, OS_MLW)
269B19_NONE = MakePlatformBundle(19)
270BCANARY_NONE = MakePepperBundle(0, stability=CANARY,
271                                bundle_name=CANARY_BUNDLE_NAME)
272B21_0_1145_0_MLW = MakePlatformBundle(21, 138079, V21_0_1145_0, OS_MLW)
273B21_0_1166_0_MW = MakePlatformBundle(21, 140819, V21_0_1166_0, OS_MW)
274B21_NONE = MakePlatformBundle(21)
275B26_NONE = MakePlatformBundle(26)
276B26_0_1386_0_MLW = MakePlatformBundle(26, 177362, V26_0_1386_0, OS_MLW)
277B26_0_1386_1_MLW = MakePlatformBundle(26, 177439, V26_0_1386_1, OS_MLW)
278BTRUNK_140819_MLW = MakePlatformBundle(21, 140819, VTRUNK_140819, OS_MLW)
279NON_PEPPER_BUNDLE_NOARCHIVES = MakeNonPepperBundle('foo')
280NON_PEPPER_BUNDLE_ARCHIVES = MakeNonPepperBundle('bar', with_archives=True)
281
282
283class TestUpdateManifest(unittest.TestCase):
284  def setUp(self):
285    self.history = MakeHistory()
286    self.files = MakeFiles()
287    self.version_mapping = {}
288    self.delegate = None
289    self.uploaded_manifest = None
290    self.manifest = None
291
292    logging.basicConfig(level=logging.CRITICAL)
293    # Uncomment the following line to enable more debugging info.
294    # logging.getLogger('update_nacl_manifest').setLevel(logging.INFO)
295
296  def _MakeDelegate(self):
297    self.delegate = TestDelegate(self.manifest, self.history.history,
298        self.files)
299
300  def _Run(self, host_oses, extra_archives=None, fixed_bundle_versions=None):
301    update_nacl_manifest.Run(self.delegate, host_oses, extra_archives,
302                             fixed_bundle_versions)
303
304  def _HasUploadedManifest(self):
305    return 'naclsdk_manifest2.json' in self.files
306
307  def _ReadUploadedManifest(self):
308    self.uploaded_manifest = manifest_util.SDKManifest()
309    self.uploaded_manifest.LoadDataFromString(
310        self.files['naclsdk_manifest2.json'])
311
312  def _AssertUploadedManifestHasBundle(self, bundle, stability,
313                                       bundle_name=None):
314    if not bundle_name:
315      bundle_name = bundle.name
316
317    uploaded_manifest_bundle = self.uploaded_manifest.GetBundle(bundle_name)
318    # Bundles that we create in the test (and in the manifest snippets) have
319    # their stability set to "dev". update_nacl_manifest correctly updates it.
320    # So we have to force the stability of |bundle| so they compare equal.
321    test_bundle = copy.copy(bundle)
322    test_bundle.stability = stability
323    if bundle_name:
324      test_bundle.name = bundle_name
325    self.assertEqual(uploaded_manifest_bundle, test_bundle)
326
327  def _AddCsvHistory(self, history):
328    import csv
329    import cStringIO
330    history_stream = cStringIO.StringIO(history)
331    self.history.history = [(platform, channel, version, date)
332        for platform, channel, version, date in csv.reader(history_stream)]
333
334  def testNoUpdateNeeded(self):
335    self.manifest = MakeManifest(B18_0_1025_163_MLW)
336    self._MakeDelegate()
337    self._Run(OS_MLW)
338    self.assertFalse(self._HasUploadedManifest())
339
340    # Add another bundle, make sure it still doesn't update
341    self.manifest.AddBundle(B19_0_1084_41_MLW)
342    self._Run(OS_MLW)
343    self.assertFalse(self._HasUploadedManifest())
344
345  def testSimpleUpdate(self):
346    self.manifest = MakeManifest(B18_NONE)
347    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
348    self.files.Add(B18_0_1025_163_MLW)
349    self._MakeDelegate()
350    self._Run(OS_MLW)
351    self._ReadUploadedManifest()
352    self._AssertUploadedManifestHasBundle(B18_0_1025_163_MLW, BETA)
353    self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
354
355  def testOnePlatformHasNewerRelease(self):
356    self.manifest = MakeManifest(B18_NONE)
357    self.history.Add(OS_M, BETA, V18_0_1025_175)  # Mac has newer version
358    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
359    self.files.Add(B18_0_1025_163_MLW)
360    self._MakeDelegate()
361    self._Run(OS_MLW)
362    self._ReadUploadedManifest()
363    self._AssertUploadedManifestHasBundle(B18_0_1025_163_MLW, BETA)
364    self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
365
366  def testMultipleMissingPlatformsInHistory(self):
367    self.manifest = MakeManifest(B18_NONE)
368    self.history.Add(OS_ML, BETA, V18_0_1025_184)
369    self.history.Add(OS_M, BETA, V18_0_1025_175)
370    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
371    self.files.Add(B18_0_1025_163_MLW)
372    self._MakeDelegate()
373    self._Run(OS_MLW)
374    self._ReadUploadedManifest()
375    self._AssertUploadedManifestHasBundle(B18_0_1025_163_MLW, BETA)
376    self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
377
378  def testUpdateOnlyOneBundle(self):
379    self.manifest = MakeManifest(B18_NONE, B19_0_1084_41_MLW)
380    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
381    self.files.Add(B18_0_1025_163_MLW)
382    self._MakeDelegate()
383    self._Run(OS_MLW)
384    self._ReadUploadedManifest()
385    self._AssertUploadedManifestHasBundle(B18_0_1025_163_MLW, BETA)
386    self._AssertUploadedManifestHasBundle(B19_0_1084_41_MLW, DEV)
387    self.assertEqual(len(self.uploaded_manifest.GetBundles()), 2)
388
389  def testUpdateTwoBundles(self):
390    self.manifest = MakeManifest(B18_NONE, B19_NONE)
391    self.history.Add(OS_MLW, DEV, V19_0_1084_41)
392    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
393    self.files.Add(B18_0_1025_163_MLW)
394    self.files.Add(B19_0_1084_41_MLW)
395    self._MakeDelegate()
396    self._Run(OS_MLW)
397    self._ReadUploadedManifest()
398    self._AssertUploadedManifestHasBundle(B18_0_1025_163_MLW, BETA)
399    self._AssertUploadedManifestHasBundle(B19_0_1084_41_MLW, DEV)
400    self.assertEqual(len(self.uploaded_manifest.GetBundles()), 2)
401
402  def testUpdateWithMissingPlatformsInArchives(self):
403    self.manifest = MakeManifest(B18_NONE)
404    self.history.Add(OS_MLW, BETA, V18_0_1025_184)
405    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
406    self.files.Add(B18_0_1025_184_MLW, add_archive_for_os=OS_M)
407    self.files.Add(B18_0_1025_163_MLW)
408    self._MakeDelegate()
409    self._Run(OS_MLW)
410    self._ReadUploadedManifest()
411    self._AssertUploadedManifestHasBundle(B18_0_1025_163_MLW, BETA)
412    self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
413
414  def testUpdateWithMissingManifestSnippets(self):
415    self.manifest = MakeManifest(B18_NONE)
416    self.history.Add(OS_MLW, BETA, V18_0_1025_184)
417    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
418    self.files.Add(B18_0_1025_184_MLW, add_json_for_os=OS_ML)
419    self.files.Add(B18_0_1025_163_MLW)
420    self._MakeDelegate()
421    self._Run(OS_MLW)
422    self._ReadUploadedManifest()
423    self._AssertUploadedManifestHasBundle(B18_0_1025_163_MLW, BETA)
424    self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
425
426  def testRecommendedIsStable(self):
427    for channel in STABLE, BETA, DEV, CANARY:
428      self.setUp()
429      bundle = copy.deepcopy(B18_NONE)
430      self.manifest = MakeManifest(bundle)
431      self.history.Add(OS_MLW, channel, V18_0_1025_163)
432      self.files.Add(B18_0_1025_163_MLW)
433      self._MakeDelegate()
434      self._Run(OS_MLW)
435      self._ReadUploadedManifest()
436      self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
437      uploaded_bundle = self.uploaded_manifest.GetBundle('pepper_18')
438      if channel == STABLE:
439        self.assertEqual(uploaded_bundle.recommended, 'yes')
440      else:
441        self.assertEqual(uploaded_bundle.recommended, 'no')
442
443  def testNoUpdateWithNonPepperBundle(self):
444    self.manifest = MakeManifest(NON_PEPPER_BUNDLE_NOARCHIVES,
445        B18_0_1025_163_MLW)
446    self._MakeDelegate()
447    self._Run(OS_MLW)
448    self.assertFalse(self._HasUploadedManifest())
449
450  def testUpdateWithHistoryWithExtraneousPlatforms(self):
451    self.manifest = MakeManifest(B18_NONE)
452    self.history.Add(OS_ML, BETA, V18_0_1025_184)
453    self.history.Add(OS_CR, BETA, V18_0_1025_184)
454    self.history.Add(OS_CR, BETA, V18_0_1025_175)
455    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
456    self.files.Add(B18_0_1025_163_MLW)
457    self._MakeDelegate()
458    self._Run(OS_MLW)
459    self._ReadUploadedManifest()
460    self._AssertUploadedManifestHasBundle(B18_0_1025_163_MLW, BETA)
461    self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
462
463  def testSnippetWithStringRevisionAndVersion(self):
464    # This test exists because some manifest snippets were uploaded with
465    # strings for their revisions and versions. I want to make sure the
466    # resulting manifest is still consistent with the old format.
467    self.manifest = MakeManifest(B18_NONE)
468    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
469    bundle_string_revision = MakePlatformBundle('18', '1234', V18_0_1025_163,
470                                                OS_MLW)
471    self.files.Add(bundle_string_revision)
472    self._MakeDelegate()
473    self._Run(OS_MLW)
474    self._ReadUploadedManifest()
475    uploaded_bundle = self.uploaded_manifest.GetBundle(
476        bundle_string_revision.name)
477    self.assertEqual(uploaded_bundle.revision, 1234)
478    self.assertEqual(uploaded_bundle.version, 18)
479
480  def testUpdateCanary(self):
481    self.manifest = MakeManifest(copy.deepcopy(BCANARY_NONE))
482    self.files.Add(BTRUNK_140819_MLW)
483    self._MakeDelegate()
484    self._Run(OS_MLW)
485    self._ReadUploadedManifest()
486    self._AssertUploadedManifestHasBundle(BTRUNK_140819_MLW, CANARY,
487                                          bundle_name=CANARY_BUNDLE_NAME)
488
489  def testCanaryShouldOnlyUseCanaryVersions(self):
490    canary_bundle = copy.deepcopy(BCANARY_NONE)
491    self.manifest = MakeManifest(canary_bundle)
492    self.history.Add(OS_MW, CANARY, V21_0_1166_0)
493    self.history.Add(OS_MW, BETA, V19_0_1084_41)
494    self.files.Add(B19_0_1084_41_MLW)
495    self.version_mapping[V21_0_1166_0] = VTRUNK_140819
496    self._MakeDelegate()
497    self.assertRaises(Exception, self._Run, OS_MLW)
498
499  def testExtensionWorksAsBz2(self):
500    # Allow old bundles with just .bz2 extension to work
501    self.manifest = MakeManifest(B18_NONE)
502    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
503    bundle = copy.deepcopy(B18_0_1025_163_MLW)
504    archive_url = bundle.GetArchive('mac').url
505    bundle.GetArchive('mac').url = archive_url.replace('.tar', '')
506    self.files.Add(bundle)
507    self._MakeDelegate()
508    self._Run(OS_MLW)
509    self._ReadUploadedManifest()
510    self._AssertUploadedManifestHasBundle(bundle, BETA)
511    self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
512
513  def testOnlyOneStableBundle(self):
514    # Make sure that any bundle that has an older version than STABLE is marked
515    # as POST_STABLE, even if the last version we found was BETA, DEV, etc.
516    for channel in STABLE, BETA, DEV, CANARY:
517      self.setUp()
518      self.manifest = MakeManifest(B18_NONE, B19_NONE)
519      self.history.Add(OS_MLW, channel, V18_0_1025_163)
520      self.history.Add(OS_MLW, STABLE, V19_0_1084_41)
521      self.files.Add(B18_0_1025_163_MLW)
522      self.files.Add(B19_0_1084_41_MLW)
523      self._MakeDelegate()
524      self._Run(OS_MLW)
525      self._ReadUploadedManifest()
526      p18_bundle = self.uploaded_manifest.GetBundle(B18_NONE.name)
527      self.assertEqual(p18_bundle.stability, POST_STABLE)
528      self.assertEqual(p18_bundle.recommended, 'no')
529      p19_bundle = self.uploaded_manifest.GetBundle(B19_NONE.name)
530      self.assertEqual(p19_bundle.stability, STABLE)
531      self.assertEqual(p19_bundle.recommended, 'yes')
532
533  def testDontPushIfNoChange(self):
534    # Make an online manifest that already has this bundle.
535    online_manifest = MakeManifest(B18_0_1025_163_MLW)
536    self.files.AddOnlineManifest(online_manifest.GetDataAsString())
537
538    self.manifest = MakeManifest(B18_NONE)
539    self.history.Add(OS_MLW, DEV, V18_0_1025_163)
540    self.files.Add(B18_0_1025_163_MLW)
541
542    self._MakeDelegate()
543    self._Run(OS_MLW)
544    self.assertFalse(self.delegate.called_gsutil_cp)
545
546  def testDontPushIfRollback(self):
547    # Make an online manifest that has a newer bundle
548    online_manifest = MakeManifest(B18_0_1025_184_MLW)
549    self.files.AddOnlineManifest(online_manifest.GetDataAsString())
550
551    self.manifest = MakeManifest(B18_NONE)
552    self.history.Add(OS_MLW, DEV, V18_0_1025_163)
553    self.files.Add(B18_0_1025_163_MLW)
554
555    self._MakeDelegate()
556    self._Run(OS_MLW)
557    self.assertFalse(self.delegate.called_gsutil_cp)
558
559  def testRunWithFixedBundleVersions(self):
560    self.manifest = MakeManifest(B18_NONE)
561    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
562    self.files.Add(B18_0_1025_163_MLW)
563    self.files.Add(B18_0_1025_184_MLW)
564
565    self._MakeDelegate()
566    self._Run(OS_MLW, fixed_bundle_versions=[('pepper_18', '18.0.1025.184')])
567    self._ReadUploadedManifest()
568    self._AssertUploadedManifestHasBundle(B18_0_1025_184_MLW, BETA)
569    self.assertEqual(len(self.uploaded_manifest.GetBundles()), 1)
570
571  def testRunWithMissingFixedBundleVersions(self):
572    self.manifest = MakeManifest(B18_NONE)
573    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
574    self.files.Add(B18_0_1025_163_MLW)
575
576    self._MakeDelegate()
577    self._Run(OS_MLW, fixed_bundle_versions=[('pepper_18', '18.0.1025.184')])
578    # Nothing should be uploaded if the user gives a missing fixed version.
579    self.assertFalse(self.delegate.called_gsutil_cp)
580
581  def testDontIncludeRandomBundles(self):
582    self.manifest = MakeManifest(B26_NONE)
583    self.history.Add(OS_MLW, BETA, V26_0_1386_0)
584    self.files.Add(B26_0_1386_0_MLW)
585
586    some_other_bundle = MakePepperBundle(26, 1, V26_0_1386_0, BETA)
587    some_other_archive = MakeNonPlatformArchive('some_other.tar.bz2',
588                                                V26_0_1386_0)
589    some_other_bundle.AddArchive(some_other_archive)
590    self.files.AddArchive(some_other_bundle, some_other_archive)
591
592    self._MakeDelegate()
593    self._Run(OS_MLW)
594    self._ReadUploadedManifest()
595    uploaded_bundle = self.uploaded_manifest.GetBundle('pepper_26')
596    self.assertEqual(1, len(uploaded_bundle.GetHostOSArchives()))
597
598  def _AddNaclportBundles(self):
599    # Add NaclPorts "bundle" for 18, 21 and 26.
600    self.manifest = MakeManifest(B18_NONE, B21_NONE, B26_NONE)
601    self.history.Add(OS_MLW, BETA, V26_0_1386_0)
602    self.files.Add(B26_0_1386_0_MLW)
603    self.history.Add(OS_MLW, BETA, V21_0_1145_0)
604    self.files.Add(B21_0_1145_0_MLW)
605    self.history.Add(OS_MLW, BETA, V18_0_1025_163)
606    self.files.Add(B18_0_1025_163_MLW)
607
608    naclports_bundle = MakePepperBundle(26, 1, V26_0_1386_0, BETA)
609    naclports_archive = MakeNonPlatformArchive('naclports.tar.bz2',
610                                               V26_0_1386_0)
611    naclports_bundle.AddArchive(naclports_archive)
612    self.files.AddArchive(naclports_bundle, naclports_archive)
613
614    naclports_bundle = MakePepperBundle(21, 1, V21_0_1145_0, BETA)
615    naclports_archive = MakeNonPlatformArchive('naclports.tar.bz2',
616                                               V21_0_1145_0)
617    naclports_bundle.AddArchive(naclports_archive)
618    self.files.AddArchive(naclports_bundle, naclports_archive)
619
620    naclports_bundle = MakePepperBundle(18, 1, V18_0_1025_163, BETA)
621    naclports_archive = MakeNonPlatformArchive('naclports.tar.bz2',
622                                               V18_0_1025_163)
623    naclports_bundle.AddArchive(naclports_archive)
624    self.files.AddArchive(naclports_bundle, naclports_archive)
625
626  def testNaclportsBundle(self):
627    self._AddNaclportBundles()
628    self._MakeDelegate()
629    extra_archives = [('naclports.tar.bz2', '19.0.0.0', '22.0.0.0')]
630    self._Run(OS_MLW, extra_archives=extra_archives)
631    self._ReadUploadedManifest()
632
633    uploaded_bundle = self.uploaded_manifest.GetBundle('pepper_26')
634    self.assertEqual(1, len(uploaded_bundle.GetHostOSArchives()))
635
636    uploaded_bundle = self.uploaded_manifest.GetBundle('pepper_21')
637    self.assertEqual(2, len(uploaded_bundle.GetHostOSArchives()))
638
639    uploaded_bundle = self.uploaded_manifest.GetBundle('pepper_18')
640    self.assertEqual(1, len(uploaded_bundle.GetHostOSArchives()))
641
642  def testKeepBundleOrder(self):
643    # This is a regression test: when a bundle is skipped (because it isn't
644    # newer than the online bundle), it was added to the end of the list.
645
646    # Make an online manifest that already has B18.
647    online_manifest = MakeManifest(B18_0_1025_163_MLW)
648    self.files.AddOnlineManifest(online_manifest.GetDataAsString())
649
650    self.manifest = MakeManifest(B18_NONE, B19_NONE)
651    self.history.Add(OS_MLW, STABLE, V18_0_1025_163)
652    self.history.Add(OS_MLW, STABLE, V19_0_1084_41)
653    self.files.Add(B18_0_1025_163_MLW)
654    self.files.Add(B19_0_1084_41_MLW)
655
656    self._MakeDelegate()
657    self._Run(OS_MLW)
658    self._ReadUploadedManifest()
659
660    # Bundle 18 should be before bundle 19.
661    bundles = self.uploaded_manifest.GetBundles()
662    self.assertEqual(2, len(bundles))
663    self.assertEqual('pepper_18', bundles[0].name)
664    self.assertEqual('pepper_19', bundles[1].name)
665
666  def testBundleWithoutHistoryUsesOnline(self):
667    online_manifest = MakeManifest(B18_0_1025_163_MLW)
668    self.files.AddOnlineManifest(online_manifest.GetDataAsString())
669
670    self.manifest = MakeManifest(B18_NONE)
671
672    self._MakeDelegate()
673    # This should not raise.
674    self._Run(OS_MLW)
675    self._ReadUploadedManifest()
676
677    # But it should have sent an email nagging the users to lock this bundle
678    # manually.
679    self.assertTrue(self.delegate.called_sendmail)
680
681    uploaded_bundle = self.uploaded_manifest.GetBundle('pepper_18')
682    self.assertEqual(uploaded_bundle, B18_0_1025_163_MLW)
683
684  def testBundleWithoutHistoryOrOnlineRaises(self):
685    self.manifest = MakeManifest(B18_NONE)
686    self._MakeDelegate()
687    self.assertRaises(update_nacl_manifest.UnknownLockedBundleException,
688                      self._Run, OS_MLW)
689
690
691class TestUpdateVitals(unittest.TestCase):
692  def setUp(self):
693    f = tempfile.NamedTemporaryFile('w', prefix="test_update_nacl_manifest")
694    self.test_file = f.name
695    f.close()
696    test_data = "Some test data"
697    self.sha1 = hashlib.sha1(test_data).hexdigest()
698    self.data_len = len(test_data)
699    with open(self.test_file, 'w') as f:
700      f.write(test_data)
701
702  def tearDown(self):
703    os.remove(self.test_file)
704
705  def testUpdateVitals(self):
706    archive = manifest_util.Archive(manifest_util.GetHostOS())
707    path = os.path.abspath(self.test_file)
708    if sys.platform == 'win32':
709      # On Windows, the path must start with three slashes, i.e.
710      # (file:///C:\whatever)
711      path = '/' + path
712    archive.url = 'file://' + path
713
714    bundle = MakePlatformBundle(18)
715    bundle.AddArchive(archive)
716    manifest = MakeManifest(bundle)
717    archive = manifest.GetBundles()[0]['archives'][0]
718
719    self.assertTrue('size' not in archive)
720    self.assertTrue('checksum' not in archive)
721    self.assertRaises(manifest_util.Error, manifest.Validate)
722
723    manifest.Validate(add_missing_info=True)
724
725    self.assertEqual(archive['size'], self.data_len)
726    self.assertEqual(archive['checksum']['sha1'], self.sha1)
727
728
729if __name__ == '__main__':
730  unittest.main()
731