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