utils_unittest.py revision 010c0bcc9c7d91c451534d95b12305649627c180
1#!/usr/bin/python
2# Copyright 2017 The Chromium OS 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
6"""unittest for utils.py
7"""
8
9import json
10import os
11import shutil
12import tempfile
13import time
14import unittest
15
16import common
17from autotest_lib.client.bin.result_tools import utils as result_utils
18from autotest_lib.client.bin.result_tools import view as result_view
19
20SIZE = 10
21EXPECTED_SUMMARY = {
22        '': {result_utils.ORIGINAL_SIZE_BYTES: 4 * SIZE,
23             result_utils.DIRS: {
24                     'file1': {result_utils.ORIGINAL_SIZE_BYTES: SIZE},
25                     'folder1': {result_utils.ORIGINAL_SIZE_BYTES: 2 * SIZE,
26                                 result_utils.DIRS: {
27                                  'file2': {
28                                      result_utils.ORIGINAL_SIZE_BYTES: SIZE},
29                                  'file3': {
30                                      result_utils.ORIGINAL_SIZE_BYTES: SIZE},
31                                  'symlink': {
32                                      result_utils.ORIGINAL_SIZE_BYTES: 0,
33                                      result_utils.DIRS: {}}}},
34                     'folder2': {result_utils.ORIGINAL_SIZE_BYTES: SIZE,
35                                 result_utils.DIRS:
36                                     {'file2':
37                                        {result_utils.ORIGINAL_SIZE_BYTES:
38                                         SIZE}},
39                                }}}}
40
41SUMMARY_1 = {
42  '': {result_utils.ORIGINAL_SIZE_BYTES: 4 * SIZE,
43       result_utils.DIRS: {
44         'file1': {result_utils.ORIGINAL_SIZE_BYTES: SIZE},
45         'file2': {result_utils.ORIGINAL_SIZE_BYTES: SIZE},
46         'folder_not_overwritten':
47            {result_utils.ORIGINAL_SIZE_BYTES: SIZE,
48             result_utils.DIRS: {
49               'file1': {result_utils.ORIGINAL_SIZE_BYTES: SIZE}}
50            },
51          'file_to_be_overwritten': {result_utils.ORIGINAL_SIZE_BYTES: SIZE},
52        }
53      }
54  }
55
56SUMMARY_2 = {
57  '': {result_utils.ORIGINAL_SIZE_BYTES: 26 * SIZE,
58       result_utils.DIRS: {
59         # `file1` exists and has the same size.
60         'file1': {result_utils.ORIGINAL_SIZE_BYTES: SIZE},
61         # Change the size of `file2` to make sure summary merge works.
62         'file2': {result_utils.ORIGINAL_SIZE_BYTES: 2 * SIZE},
63         # `file3` is new.
64         'file3': {result_utils.ORIGINAL_SIZE_BYTES: SIZE},
65         # Add a new sub-directory.
66         'folder1': {result_utils.ORIGINAL_SIZE_BYTES: 20 * SIZE,
67                     result_utils.TRIMMED_SIZE_BYTES: SIZE,
68                     result_utils.DIRS: {
69                         # Add a file being trimmed.
70                         'file4': {
71                           result_utils.ORIGINAL_SIZE_BYTES: 20 * SIZE,
72                           result_utils.TRIMMED_SIZE_BYTES: SIZE}
73                         }
74                     },
75          # Add a file whose name collides with the previous summary.
76          'folder_not_overwritten': {
77            result_utils.ORIGINAL_SIZE_BYTES: 100 * SIZE},
78          # Add a directory whose name collides with the previous summary.
79          'file_to_be_overwritten':
80            {result_utils.ORIGINAL_SIZE_BYTES: SIZE,
81             result_utils.DIRS: {
82               'file1': {result_utils.ORIGINAL_SIZE_BYTES: SIZE}}
83            },
84          # Folder was collected, not missing from the final result folder.
85          'folder_tobe_deleted':
86            {result_utils.ORIGINAL_SIZE_BYTES: SIZE,
87             result_utils.DIRS: {
88               'file_tobe_deleted': {result_utils.ORIGINAL_SIZE_BYTES: SIZE}}
89            },
90        }
91      }
92  }
93
94SUMMARY_1_SIZE = 171
95SUMMARY_2_SIZE = 345
96
97# The final result dir has an extra folder and file, also with `file3` removed
98# to test the case that client files are removed on the server side.
99EXPECTED_MERGED_SUMMARY = {
100  '': {result_utils.ORIGINAL_SIZE_BYTES:
101           37 * SIZE + SUMMARY_1_SIZE + SUMMARY_2_SIZE,
102       result_utils.TRIMMED_SIZE_BYTES:
103           17 * SIZE + SUMMARY_1_SIZE + SUMMARY_2_SIZE,
104       # Size collected is SIZE bytes more than total size as an old `file2` of
105       # SIZE bytes is overwritten by a newer file.
106       result_utils.COLLECTED_SIZE_BYTES:
107           19 * SIZE + SUMMARY_1_SIZE + SUMMARY_2_SIZE,
108       result_utils.DIRS: {
109         'dir_summary_1.json': {
110           result_utils.ORIGINAL_SIZE_BYTES: SUMMARY_1_SIZE},
111         'dir_summary_2.json': {
112           result_utils.ORIGINAL_SIZE_BYTES: SUMMARY_2_SIZE},
113         'file1': {result_utils.ORIGINAL_SIZE_BYTES: SIZE},
114         'file2': {result_utils.ORIGINAL_SIZE_BYTES: 2 * SIZE,
115                   result_utils.COLLECTED_SIZE_BYTES: 3 * SIZE,
116                   result_utils.TRIMMED_SIZE_BYTES: 2 * SIZE},
117         'file3': {result_utils.ORIGINAL_SIZE_BYTES: SIZE},
118         'folder1': {result_utils.ORIGINAL_SIZE_BYTES: 20 * SIZE,
119                     result_utils.TRIMMED_SIZE_BYTES: SIZE,
120                     result_utils.COLLECTED_SIZE_BYTES: SIZE,
121                     result_utils.DIRS: {
122                         'file4': {result_utils.ORIGINAL_SIZE_BYTES: 20 * SIZE,
123                                   result_utils.TRIMMED_SIZE_BYTES: SIZE}
124                         }
125                     },
126         'folder2': {result_utils.ORIGINAL_SIZE_BYTES: 10 * SIZE,
127                     result_utils.COLLECTED_SIZE_BYTES: 10 * SIZE,
128                     result_utils.TRIMMED_SIZE_BYTES: 10 * SIZE,
129                     result_utils.DIRS: {
130                         'server_file': {
131                           result_utils.ORIGINAL_SIZE_BYTES: 10 * SIZE}
132                         }
133                     },
134         'folder_not_overwritten':
135            {result_utils.ORIGINAL_SIZE_BYTES: SIZE,
136             result_utils.COLLECTED_SIZE_BYTES: SIZE,
137             result_utils.TRIMMED_SIZE_BYTES: SIZE,
138             result_utils.DIRS: {
139               'file1': {result_utils.ORIGINAL_SIZE_BYTES: SIZE}}
140            },
141         'file_to_be_overwritten':
142           {result_utils.ORIGINAL_SIZE_BYTES: SIZE,
143            result_utils.COLLECTED_SIZE_BYTES: SIZE,
144            result_utils.TRIMMED_SIZE_BYTES: SIZE,
145            result_utils.DIRS: {
146              'file1': {result_utils.ORIGINAL_SIZE_BYTES: SIZE}}
147           },
148         'folder_tobe_deleted':
149           {result_utils.ORIGINAL_SIZE_BYTES: SIZE,
150            result_utils.COLLECTED_SIZE_BYTES: SIZE,
151            result_utils.TRIMMED_SIZE_BYTES: 0,
152            result_utils.DIRS: {
153              'file_tobe_deleted': {result_utils.ORIGINAL_SIZE_BYTES: SIZE,
154                                    result_utils.COLLECTED_SIZE_BYTES: SIZE,
155                                    result_utils.TRIMMED_SIZE_BYTES: 0}}
156           },
157        }
158      }
159  }
160
161def create_file(path, size=SIZE):
162    """Create a temp file at given path with the given size.
163
164    @param path: Path to the temp file.
165    @param size: Size of the temp file, default to SIZE.
166    """
167    with open(path, 'w') as f:
168        f.write('A' * size)
169
170
171class GetDirSummaryTest(unittest.TestCase):
172    """Test class for get_dir_summary method"""
173
174    def setUp(self):
175        """Setup directory for test."""
176        self.test_dir = tempfile.mkdtemp()
177        file1 = os.path.join(self.test_dir, 'file1')
178        create_file(file1)
179        folder1 = os.path.join(self.test_dir, 'folder1')
180        os.mkdir(folder1)
181        file2 = os.path.join(folder1, 'file2')
182        create_file(file2)
183        file3 = os.path.join(folder1, 'file3')
184        create_file(file3)
185
186        folder2 = os.path.join(self.test_dir, 'folder2')
187        os.mkdir(folder2)
188        file4 = os.path.join(folder2, 'file2')
189        create_file(file4)
190
191        symlink = os.path.join(folder1, 'symlink')
192        os.symlink(folder2, symlink)
193
194    def tearDown(self):
195        """Cleanup the test directory."""
196        shutil.rmtree(self.test_dir, ignore_errors=True)
197
198    def test_get_dir_summary(self):
199        """Test method get_dir_summary."""
200        summary_json = result_utils.get_dir_summary(
201                self.test_dir + '/', self.test_dir + '/')
202        self.assertEqual(EXPECTED_SUMMARY, summary_json)
203
204
205class MergeSummaryTest(unittest.TestCase):
206    """Test class for merge_summaries method"""
207
208    def setUp(self):
209        """Setup directory to match the file structure in MERGED_SUMMARY."""
210        self.test_dir = tempfile.mkdtemp() + '/'
211        file1 = os.path.join(self.test_dir, 'file1')
212        create_file(file1)
213        file2 = os.path.join(self.test_dir, 'file2')
214        create_file(file2, 2*SIZE)
215        file3 = os.path.join(self.test_dir, 'file3')
216        create_file(file3, SIZE)
217        folder1 = os.path.join(self.test_dir, 'folder1')
218        os.mkdir(folder1)
219        file4 = os.path.join(folder1, 'file4')
220        create_file(file4, SIZE)
221        folder2 = os.path.join(self.test_dir, 'folder2')
222        os.mkdir(folder2)
223        server_file = os.path.join(folder2, 'server_file')
224        create_file(server_file, 10*SIZE)
225        folder_not_overwritten = os.path.join(
226                self.test_dir, 'folder_not_overwritten')
227        os.mkdir(folder_not_overwritten)
228        file1 = os.path.join(folder_not_overwritten, 'file1')
229        create_file(file1)
230        file_to_be_overwritten = os.path.join(
231                self.test_dir, 'file_to_be_overwritten')
232        os.mkdir(file_to_be_overwritten)
233        file1 = os.path.join(file_to_be_overwritten, 'file1')
234        create_file(file1)
235
236        # Save summary file to test_dir
237        self.summary_1 = os.path.join(self.test_dir, 'dir_summary_1.json')
238        with open(self.summary_1, 'w') as f:
239            json.dump(SUMMARY_1, f)
240        # Wait for 10ms, to make sure summary_2 has a later time stamp.
241        time.sleep(0.01)
242        self.summary_2 = os.path.join(self.test_dir, 'dir_summary_2.json')
243        with open(self.summary_2, 'w') as f:
244            json.dump(SUMMARY_2, f)
245
246    def tearDown(self):
247        """Cleanup the test directory."""
248        shutil.rmtree(self.test_dir, ignore_errors=True)
249
250    def testMergeSummaries(self):
251        """Test method merge_summaries."""
252        client_collected_bytes, merged_summary = result_utils.merge_summaries(
253                self.test_dir)
254        self.assertEqual(EXPECTED_MERGED_SUMMARY, merged_summary)
255        self.assertEqual(client_collected_bytes, 9 * SIZE)
256
257    def testMergeSummariesFromNoHistory(self):
258        """Test method merge_summaries can handle results with no existing
259        summary.
260        """
261        os.remove(self.summary_1)
262        os.remove(self.summary_2)
263        client_collected_bytes, _ = result_utils.merge_summaries(self.test_dir)
264        self.assertEqual(client_collected_bytes, 0)
265
266    def testBuildView(self):
267        """Test build method in result_view module."""
268        client_collected_bytes, summary = result_utils.merge_summaries(
269                self.test_dir)
270        html_file = os.path.join(self.test_dir,
271                                 result_view.DEFAULT_RESULT_SUMMARY_NAME)
272        result_view.build(client_collected_bytes, summary, html_file)
273        # Make sure html_file is created with content.
274        self.assertGreater(os.stat(html_file).st_size, 1000)
275
276
277# this is so the test can be run in standalone mode
278if __name__ == '__main__':
279    """Main"""
280    unittest.main()
281