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 fnmatch
7import tempfile
8import unittest
9import zipfile
10
11import md5_check # pylint: disable=W0403
12
13
14def _WriteZipFile(path, entries):
15  with zipfile.ZipFile(path, 'w') as zip_file:
16    for subpath, data in entries:
17      zip_file.writestr(subpath, data)
18
19
20class TestMd5Check(unittest.TestCase):
21  def setUp(self):
22    self.called = False
23    self.changes = None
24
25  def testCallAndRecordIfStale(self):
26    input_strings = ['string1', 'string2']
27    input_file1 = tempfile.NamedTemporaryFile(suffix='.txt')
28    input_file2 = tempfile.NamedTemporaryFile(suffix='.zip')
29    file1_contents = 'input file 1'
30    input_file1.write(file1_contents)
31    input_file1.flush()
32    # Test out empty zip file to start.
33    _WriteZipFile(input_file2.name, [])
34    input_files = [input_file1.name, input_file2.name]
35
36    record_path = tempfile.NamedTemporaryFile(suffix='.stamp')
37
38    def CheckCallAndRecord(should_call, message, force=False,
39                           outputs_specified=False, outputs_missing=False,
40                           expected_changes=None, added_or_modified_only=None):
41      output_paths = None
42      if outputs_specified:
43        output_file1 = tempfile.NamedTemporaryFile()
44        if outputs_missing:
45          output_file1.close()  # Gets deleted on close().
46        output_paths = [output_file1.name]
47
48      self.called = False
49      self.changes = None
50      if expected_changes or added_or_modified_only is not None:
51        def MarkCalled(changes):
52          self.called = True
53          self.changes = changes
54      else:
55        def MarkCalled():
56          self.called = True
57
58      md5_check.CallAndRecordIfStale(
59          MarkCalled,
60          record_path=record_path.name,
61          input_paths=input_files,
62          input_strings=input_strings,
63          output_paths=output_paths,
64          force=force,
65          pass_changes=(expected_changes or added_or_modified_only) is not None)
66      self.assertEqual(should_call, self.called, message)
67      if expected_changes:
68        description = self.changes.DescribeDifference()
69        self.assertTrue(fnmatch.fnmatch(description, expected_changes),
70                        'Expected %s to match %s' % (
71                        repr(description), repr(expected_changes)))
72      if should_call and added_or_modified_only is not None:
73        self.assertEqual(added_or_modified_only,
74                         self.changes.AddedOrModifiedOnly())
75
76    CheckCallAndRecord(True, 'should call when record doesn\'t exist',
77                       expected_changes='Previous stamp file not found.',
78                       added_or_modified_only=False)
79    CheckCallAndRecord(False, 'should not call when nothing changed')
80    CheckCallAndRecord(False, 'should not call when nothing changed #2',
81                       outputs_specified=True, outputs_missing=False)
82    CheckCallAndRecord(True, 'should call when output missing',
83                       outputs_specified=True, outputs_missing=True,
84                       expected_changes='Outputs do not exist:*',
85                       added_or_modified_only=False)
86    CheckCallAndRecord(True, force=True, message='should call when forced',
87                       expected_changes='force=True',
88                       added_or_modified_only=False)
89
90    input_file1.write('some more input')
91    input_file1.flush()
92    CheckCallAndRecord(True, 'changed input file should trigger call',
93                       expected_changes='*Modified: %s' % input_file1.name,
94                       added_or_modified_only=True)
95
96    input_files = input_files[::-1]
97    CheckCallAndRecord(False, 'reordering of inputs shouldn\'t trigger call')
98
99    input_files = input_files[:1]
100    CheckCallAndRecord(True, 'removing file should trigger call',
101                       expected_changes='*Removed: %s' % input_file1.name,
102                       added_or_modified_only=False)
103
104    input_files.append(input_file1.name)
105    CheckCallAndRecord(True, 'added input file should trigger call',
106                       expected_changes='*Added: %s' % input_file1.name,
107                       added_or_modified_only=True)
108
109    input_strings[0] = input_strings[0] + ' a bit longer'
110    CheckCallAndRecord(True, 'changed input string should trigger call',
111                       expected_changes='*Input strings changed*',
112                       added_or_modified_only=False)
113
114    input_strings = input_strings[::-1]
115    CheckCallAndRecord(True, 'reordering of string inputs should trigger call',
116                       expected_changes='*Input strings changed*')
117
118    input_strings = input_strings[:1]
119    CheckCallAndRecord(True, 'removing a string should trigger call')
120
121    input_strings.append('a brand new string')
122    CheckCallAndRecord(True, 'added input string should trigger call')
123
124    _WriteZipFile(input_file2.name, [('path/1.txt', '1')])
125    CheckCallAndRecord(True, 'added subpath should trigger call',
126                       expected_changes='*Modified: %s*Subpath added: %s' % (
127                                        input_file2.name, 'path/1.txt'),
128                       added_or_modified_only=True)
129    _WriteZipFile(input_file2.name, [('path/1.txt', '2')])
130    CheckCallAndRecord(True, 'changed subpath should trigger call',
131                       expected_changes='*Modified: %s*Subpath modified: %s' % (
132                                        input_file2.name, 'path/1.txt'),
133                       added_or_modified_only=True)
134    CheckCallAndRecord(False, 'should not call when nothing changed')
135
136    _WriteZipFile(input_file2.name, [])
137    CheckCallAndRecord(True, 'removed subpath should trigger call',
138                       expected_changes='*Modified: %s*Subpath removed: %s' % (
139                                        input_file2.name, 'path/1.txt'),
140                       added_or_modified_only=False)
141
142
143if __name__ == '__main__':
144  unittest.main()
145