xref: /aosp_15_r20/external/autotest/client/bin/site_sysinfo_unittest.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li#!/usr/bin/python3
2*9c5db199SXin Li
3*9c5db199SXin Li"""Tests for site_sysinfo."""
4*9c5db199SXin Li
5*9c5db199SXin Lifrom __future__ import absolute_import
6*9c5db199SXin Lifrom __future__ import division
7*9c5db199SXin Lifrom __future__ import print_function
8*9c5db199SXin Li
9*9c5db199SXin Li
10*9c5db199SXin Li__author__ = '[email protected] (Dan Shi)'
11*9c5db199SXin Li
12*9c5db199SXin Liimport six.moves.cPickle as pickle
13*9c5db199SXin Liimport filecmp
14*9c5db199SXin Liimport os
15*9c5db199SXin Liimport random
16*9c5db199SXin Liimport shutil
17*9c5db199SXin Liimport tempfile
18*9c5db199SXin Liimport unittest
19*9c5db199SXin Li
20*9c5db199SXin Liimport common
21*9c5db199SXin Lifrom autotest_lib.client.bin import site_sysinfo
22*9c5db199SXin Lifrom autotest_lib.client.common_lib import autotemp
23*9c5db199SXin Lifrom six.moves import range
24*9c5db199SXin Lifrom six.moves import zip
25*9c5db199SXin Li
26*9c5db199SXin Li
27*9c5db199SXin Liclass diffable_logdir_test(unittest.TestCase):
28*9c5db199SXin Li    """Tests for methods in class diffable_logdir."""
29*9c5db199SXin Li
30*9c5db199SXin Li
31*9c5db199SXin Li    def setUp(self):
32*9c5db199SXin Li        """Initialize a temp direcotry with test files."""
33*9c5db199SXin Li        self.tempdir = autotemp.tempdir(unique_id='diffable_logdir')
34*9c5db199SXin Li        self.src_dir = os.path.join(self.tempdir.name, 'src')
35*9c5db199SXin Li        self.dest_dir = os.path.join(self.tempdir.name, 'dest')
36*9c5db199SXin Li
37*9c5db199SXin Li        self.existing_files = ['existing_file_'+str(i) for i in range(3)]
38*9c5db199SXin Li        self.existing_files_folder = ['', 'sub', 'sub/sub2']
39*9c5db199SXin Li        self.existing_files_path = [os.path.join(self.src_dir, folder, f)
40*9c5db199SXin Li                                    for f,folder in zip(self.existing_files,
41*9c5db199SXin Li                                                self.existing_files_folder)]
42*9c5db199SXin Li        self.new_files = ['new_file_'+str(i) for i in range(2)]
43*9c5db199SXin Li        self.new_files_folder = ['sub', 'sub/sub3']
44*9c5db199SXin Li        self.new_files_path = [os.path.join(self.src_dir, folder, f)
45*9c5db199SXin Li                                    for f,folder in zip(self.new_files,
46*9c5db199SXin Li                                                self.new_files_folder)]
47*9c5db199SXin Li
48*9c5db199SXin Li        # Create some file with random data in source directory.
49*9c5db199SXin Li        for p in self.existing_files_path:
50*9c5db199SXin Li            self.append_text_to_file(str(random.random()), p)
51*9c5db199SXin Li
52*9c5db199SXin Li        self.existing_fifo_path = os.path.join(
53*9c5db199SXin Li            self.src_dir,'sub/sub2/existing_fifo')
54*9c5db199SXin Li        os.mkfifo(self.existing_fifo_path)
55*9c5db199SXin Li
56*9c5db199SXin Li
57*9c5db199SXin Li    def tearDown(self):
58*9c5db199SXin Li        """Clearn up."""
59*9c5db199SXin Li        self.tempdir.clean()
60*9c5db199SXin Li
61*9c5db199SXin Li
62*9c5db199SXin Li    def append_text_to_file(self, text, file_path):
63*9c5db199SXin Li        """Append text to the end of a file, create the file if not existed.
64*9c5db199SXin Li
65*9c5db199SXin Li        @param text: text to be appended to a file.
66*9c5db199SXin Li        @param file_path: path to the file.
67*9c5db199SXin Li
68*9c5db199SXin Li        """
69*9c5db199SXin Li        dir_name = os.path.dirname(file_path)
70*9c5db199SXin Li        if not os.path.exists(dir_name):
71*9c5db199SXin Li            os.makedirs(dir_name)
72*9c5db199SXin Li        with open(file_path, 'a') as f:
73*9c5db199SXin Li            f.write(text)
74*9c5db199SXin Li
75*9c5db199SXin Li
76*9c5db199SXin Li    def get_dest_path(self, src_path):
77*9c5db199SXin Li        """Get file path in dest dir from the one in src dir.
78*9c5db199SXin Li
79*9c5db199SXin Li        @param src_path: File path in src dir.
80*9c5db199SXin Li
81*9c5db199SXin Li        """
82*9c5db199SXin Li        # Make sure src_path is a subpath of self.src_dir
83*9c5db199SXin Li        self.assertEqual(os.path.commonprefix((src_path, self.src_dir)),
84*9c5db199SXin Li                         self.src_dir)
85*9c5db199SXin Li        rel_path = os.path.relpath(src_path, self.src_dir)
86*9c5db199SXin Li        return os.path.join(self.dest_dir, rel_path)
87*9c5db199SXin Li
88*9c5db199SXin Li    def assert_trees_equal(self, dir1, dir2, ignore=None):
89*9c5db199SXin Li        """Assert two directory trees contain the same files.
90*9c5db199SXin Li
91*9c5db199SXin Li        @param dir1: the left comparison directory.
92*9c5db199SXin Li        @param dir2: the right comparison directory.
93*9c5db199SXin Li        @param ignore: filenames to ignore (in any directory).
94*9c5db199SXin Li
95*9c5db199SXin Li        """
96*9c5db199SXin Li        dircmps = []
97*9c5db199SXin Li        dircmps.append(filecmp.dircmp(dir1, dir2, ignore))
98*9c5db199SXin Li        while dircmps:
99*9c5db199SXin Li            dcmp = dircmps.pop()
100*9c5db199SXin Li            self.assertEqual(dcmp.left_list, dcmp.right_list)
101*9c5db199SXin Li            self.assertEqual([], dcmp.diff_files)
102*9c5db199SXin Li            dircmps.extend(dcmp.subdirs.values())
103*9c5db199SXin Li
104*9c5db199SXin Li
105*9c5db199SXin Li    def test_diffable_logdir_success(self):
106*9c5db199SXin Li        """Test the diff function to save new data from a directory."""
107*9c5db199SXin Li        info = site_sysinfo.diffable_logdir(self.src_dir,
108*9c5db199SXin Li                                            keep_file_hierarchy=False,
109*9c5db199SXin Li                                            append_diff_in_name=False)
110*9c5db199SXin Li        # Run the first time to collect file status.
111*9c5db199SXin Li        info.run(log_dir=None, collect_init_status=True)
112*9c5db199SXin Li
113*9c5db199SXin Li        # Add new files to the test directory.
114*9c5db199SXin Li        for file_name, file_path in zip(self.new_files,
115*9c5db199SXin Li                                         self.new_files_path):
116*9c5db199SXin Li            self.append_text_to_file(file_name, file_path)
117*9c5db199SXin Li
118*9c5db199SXin Li        # Temp file for existing_file_2, used to hold on the inode. If the
119*9c5db199SXin Li        # file is deleted and recreated, its inode might not change.
120*9c5db199SXin Li        existing_file_2 = self.existing_files_path[2]
121*9c5db199SXin Li        existing_file_2_tmp =  existing_file_2 + '_tmp'
122*9c5db199SXin Li        os.rename(existing_file_2, existing_file_2_tmp)
123*9c5db199SXin Li
124*9c5db199SXin Li        # Append data to existing file.
125*9c5db199SXin Li        for file_name, file_path in zip(self.existing_files,
126*9c5db199SXin Li                                         self.existing_files_path):
127*9c5db199SXin Li            self.append_text_to_file(file_name, file_path)
128*9c5db199SXin Li
129*9c5db199SXin Li        # Remove the tmp file.
130*9c5db199SXin Li        os.remove(existing_file_2_tmp)
131*9c5db199SXin Li
132*9c5db199SXin Li        # Add a new FIFO
133*9c5db199SXin Li        new_fifo_path = os.path.join(self.src_dir, 'sub/sub2/new_fifo')
134*9c5db199SXin Li        os.mkfifo(new_fifo_path)
135*9c5db199SXin Li
136*9c5db199SXin Li        # Run the second time to do diff.
137*9c5db199SXin Li        info.run(self.dest_dir, collect_init_status=False, collect_all=True)
138*9c5db199SXin Li
139*9c5db199SXin Li        # Validate files in dest_dir.
140*9c5db199SXin Li        for file_name, file_path in zip(self.existing_files+self.new_files,
141*9c5db199SXin Li                                self.existing_files_path+self.new_files_path):
142*9c5db199SXin Li            file_path = self.get_dest_path(file_path)
143*9c5db199SXin Li            with open(file_path, 'r') as f:
144*9c5db199SXin Li                self.assertEqual(file_name, f.read())
145*9c5db199SXin Li
146*9c5db199SXin Li        # Assert that FIFOs are not in the diff.
147*9c5db199SXin Li        self.assertFalse(os.path.exists(
148*9c5db199SXin Li                self.get_dest_path(self.existing_fifo_path)),
149*9c5db199SXin Li                         msg='Existing FIFO present in diff sysinfo')
150*9c5db199SXin Li        self.assertFalse(os.path.exists(self.get_dest_path(new_fifo_path)),
151*9c5db199SXin Li                         msg='New FIFO present in diff sysinfo')
152*9c5db199SXin Li
153*9c5db199SXin Li        # With collect_all=True, full sysinfo should also be present.
154*9c5db199SXin Li        full_sysinfo_path = self.dest_dir + self.src_dir
155*9c5db199SXin Li        self.assertTrue(os.path.exists(full_sysinfo_path),
156*9c5db199SXin Li                        msg='Full sysinfo not present')
157*9c5db199SXin Li
158*9c5db199SXin Li        # Assert that the full sysinfo is present.
159*9c5db199SXin Li        self.assertNotEqual(self.src_dir, full_sysinfo_path)
160*9c5db199SXin Li        self.assert_trees_equal(self.src_dir, full_sysinfo_path)
161*9c5db199SXin Li
162*9c5db199SXin Li
163*9c5db199SXin Liclass LogdirTestCase(unittest.TestCase):
164*9c5db199SXin Li    """Tests logdir.run"""
165*9c5db199SXin Li
166*9c5db199SXin Li    def setUp(self):
167*9c5db199SXin Li        self.tempdir = tempfile.mkdtemp()
168*9c5db199SXin Li        self.addCleanup(shutil.rmtree, self.tempdir)
169*9c5db199SXin Li
170*9c5db199SXin Li        self.from_dir = os.path.join(self.tempdir, 'from')
171*9c5db199SXin Li        self.to_dir = os.path.join(self.tempdir, 'to')
172*9c5db199SXin Li        os.mkdir(self.from_dir)
173*9c5db199SXin Li        os.mkdir(self.to_dir)
174*9c5db199SXin Li
175*9c5db199SXin Li    def _destination_path(self, relative_path, from_dir=None):
176*9c5db199SXin Li        """The expected destination path for a subdir of the source directory"""
177*9c5db199SXin Li        if from_dir is None:
178*9c5db199SXin Li            from_dir = self.from_dir
179*9c5db199SXin Li        return '%s%s' % (self.to_dir, os.path.join(from_dir, relative_path))
180*9c5db199SXin Li
181*9c5db199SXin Li    def test_run_recreates_absolute_source_path(self):
182*9c5db199SXin Li        """When copying files, the absolute path from the source is recreated
183*9c5db199SXin Li        in the destination folder.
184*9c5db199SXin Li        """
185*9c5db199SXin Li        os.mkdir(os.path.join(self.from_dir, 'fubar'))
186*9c5db199SXin Li        logdir = site_sysinfo.logdir(self.from_dir)
187*9c5db199SXin Li        logdir.run(self.to_dir)
188*9c5db199SXin Li        destination_path= self._destination_path('fubar')
189*9c5db199SXin Li        self.assertTrue(os.path.exists(destination_path),
190*9c5db199SXin Li                        msg='Failed to copy to %s' % destination_path)
191*9c5db199SXin Li
192*9c5db199SXin Li    def test_run_skips_symlinks(self):
193*9c5db199SXin Li        os.mkdir(os.path.join(self.from_dir, 'real'))
194*9c5db199SXin Li        os.symlink(os.path.join(self.from_dir, 'real'),
195*9c5db199SXin Li                   os.path.join(self.from_dir, 'symlink'))
196*9c5db199SXin Li
197*9c5db199SXin Li        logdir = site_sysinfo.logdir(self.from_dir)
198*9c5db199SXin Li        logdir.run(self.to_dir)
199*9c5db199SXin Li
200*9c5db199SXin Li        destination_path_real = self._destination_path('real')
201*9c5db199SXin Li        self.assertTrue(os.path.exists(destination_path_real),
202*9c5db199SXin Li                        msg='real directory was not copied to %s' %
203*9c5db199SXin Li                        destination_path_real)
204*9c5db199SXin Li        destination_path_link = self._destination_path('symlink')
205*9c5db199SXin Li        self.assertFalse(
206*9c5db199SXin Li                os.path.exists(destination_path_link),
207*9c5db199SXin Li                msg='symlink was copied to %s' % destination_path_link)
208*9c5db199SXin Li
209*9c5db199SXin Li    def test_run_resolves_symlinks_to_source_root(self):
210*9c5db199SXin Li        """run tries hard to get to the source directory before copying.
211*9c5db199SXin Li
212*9c5db199SXin Li        Within the source folder, we skip any symlinks, but we first try to
213*9c5db199SXin Li        resolve symlinks to the source root itself.
214*9c5db199SXin Li        """
215*9c5db199SXin Li        os.mkdir(os.path.join(self.from_dir, 'fubar'))
216*9c5db199SXin Li        from_symlink = os.path.join(self.tempdir, 'from_symlink')
217*9c5db199SXin Li        os.symlink(self.from_dir, from_symlink)
218*9c5db199SXin Li
219*9c5db199SXin Li        logdir = site_sysinfo.logdir(from_symlink)
220*9c5db199SXin Li        logdir.run(self.to_dir)
221*9c5db199SXin Li
222*9c5db199SXin Li        destination_path = self._destination_path('fubar')
223*9c5db199SXin Li        self.assertTrue(os.path.exists(destination_path),
224*9c5db199SXin Li                        msg='Failed to copy to %s' % destination_path)
225*9c5db199SXin Li
226*9c5db199SXin Li    def test_run_excludes_common_patterns(self):
227*9c5db199SXin Li        os.mkdir(os.path.join(self.from_dir, 'autoserv2344'))
228*9c5db199SXin Li        # Create empty file.
229*9c5db199SXin Li        open(os.path.join(self.from_dir, 'system.journal'), 'w').close()
230*9c5db199SXin Li        deeper_subdir = os.path.join('prefix', 'autoserv', 'suffix')
231*9c5db199SXin Li        os.makedirs(os.path.join(self.from_dir, deeper_subdir))
232*9c5db199SXin Li
233*9c5db199SXin Li        logdir = site_sysinfo.logdir(self.from_dir)
234*9c5db199SXin Li        logdir.run(self.to_dir)
235*9c5db199SXin Li
236*9c5db199SXin Li        destination_path = self._destination_path('autoserv2344')
237*9c5db199SXin Li        self.assertFalse(os.path.exists(destination_path),
238*9c5db199SXin Li                         msg='Copied banned file to %s' % destination_path)
239*9c5db199SXin Li        destination_path = self._destination_path(deeper_subdir)
240*9c5db199SXin Li        self.assertFalse(os.path.exists(destination_path),
241*9c5db199SXin Li                         msg='Copied banned file to %s' % destination_path)
242*9c5db199SXin Li        destination_path = self._destination_path('system.journal')
243*9c5db199SXin Li        self.assertFalse(os.path.exists(destination_path),
244*9c5db199SXin Li                         msg='Copied banned file to %s' % destination_path)
245*9c5db199SXin Li
246*9c5db199SXin Li    def test_run_ignores_exclude_patterns_in_leading_dirs(self):
247*9c5db199SXin Li        """Exclude patterns should only be applied to path suffixes within
248*9c5db199SXin Li        from_dir, not to the root directory itself.
249*9c5db199SXin Li        """
250*9c5db199SXin Li        exclude_pattern_dir = os.path.join(self.from_dir, 'autoserv2344')
251*9c5db199SXin Li        os.makedirs(os.path.join(exclude_pattern_dir, 'fubar'))
252*9c5db199SXin Li        logdir = site_sysinfo.logdir(exclude_pattern_dir)
253*9c5db199SXin Li        logdir.run(self.to_dir)
254*9c5db199SXin Li        destination_path = self._destination_path('fubar',
255*9c5db199SXin Li                                                  from_dir=exclude_pattern_dir)
256*9c5db199SXin Li        self.assertTrue(os.path.exists(destination_path),
257*9c5db199SXin Li                        msg='Failed to copy to %s' % destination_path)
258*9c5db199SXin Li
259*9c5db199SXin Li    def test_pickle_unpickle_equal(self):
260*9c5db199SXin Li        """Check pickle-unpickle round-trip."""
261*9c5db199SXin Li        logdir = site_sysinfo.logdir(
262*9c5db199SXin Li                self.from_dir,
263*9c5db199SXin Li                excludes=(site_sysinfo.logdir.DEFAULT_EXCLUDES + ('a',)))
264*9c5db199SXin Li        # base_job uses protocol 2 to pickle. We follow suit.
265*9c5db199SXin Li        logdir_pickle = pickle.dumps(logdir, protocol=2)
266*9c5db199SXin Li        unpickled_logdir = pickle.loads(logdir_pickle)
267*9c5db199SXin Li
268*9c5db199SXin Li        self.assertEqual(unpickled_logdir, logdir)
269*9c5db199SXin Li
270*9c5db199SXin Li    def test_pickle_enforce_required_attributes(self):
271*9c5db199SXin Li        """Some attributes from this object should never be dropped.
272*9c5db199SXin Li
273*9c5db199SXin Li        When running a client test against an older build, we pickle the objects
274*9c5db199SXin Li        of this class from newer version of the class and unpicle to older
275*9c5db199SXin Li        versions of the class. The older versions require some attributes to be
276*9c5db199SXin Li        present.
277*9c5db199SXin Li        """
278*9c5db199SXin Li        logdir = site_sysinfo.logdir(
279*9c5db199SXin Li                self.from_dir,
280*9c5db199SXin Li                excludes=(site_sysinfo.logdir.DEFAULT_EXCLUDES + ('a',)))
281*9c5db199SXin Li        # base_job uses protocol 2 to pickle. We follow suit.
282*9c5db199SXin Li        logdir_pickle = pickle.dumps(logdir, protocol=2)
283*9c5db199SXin Li        logdir = pickle.loads(logdir_pickle)
284*9c5db199SXin Li
285*9c5db199SXin Li        self.assertEqual(logdir.additional_exclude, 'a')
286*9c5db199SXin Li
287*9c5db199SXin Li    def test_pickle_enforce_required_attributes_default(self):
288*9c5db199SXin Li        """Some attributes from this object should never be dropped.
289*9c5db199SXin Li
290*9c5db199SXin Li        When running a client test against an older build, we pickle the objects
291*9c5db199SXin Li        of this class from newer version of the class and unpicle to older
292*9c5db199SXin Li        versions of the class. The older versions require some attributes to be
293*9c5db199SXin Li        present.
294*9c5db199SXin Li        """
295*9c5db199SXin Li        logdir = site_sysinfo.logdir(self.from_dir)
296*9c5db199SXin Li        # base_job uses protocol 2 to pickle. We follow suit.
297*9c5db199SXin Li        logdir_pickle = pickle.dumps(logdir, protocol=2)
298*9c5db199SXin Li        logdir = pickle.loads(logdir_pickle)
299*9c5db199SXin Li
300*9c5db199SXin Li        self.assertEqual(logdir.additional_exclude, None)
301*9c5db199SXin Li
302*9c5db199SXin Li    def test_unpickle_handle_missing__excludes(self):
303*9c5db199SXin Li        """Accurately handle missing _excludes attribute from pickles
304*9c5db199SXin Li
305*9c5db199SXin Li        This can happen when running brand new version of this class that
306*9c5db199SXin Li        introduced this attribute from older server side code in prod.
307*9c5db199SXin Li        """
308*9c5db199SXin Li        logdir = site_sysinfo.logdir(self.from_dir)
309*9c5db199SXin Li        delattr(logdir, '_excludes')
310*9c5db199SXin Li        # base_job uses protocol 2 to pickle. We follow suit.
311*9c5db199SXin Li        logdir_pickle = pickle.dumps(logdir, protocol=2)
312*9c5db199SXin Li        logdir = pickle.loads(logdir_pickle)
313*9c5db199SXin Li
314*9c5db199SXin Li        self.assertEqual(logdir._excludes,
315*9c5db199SXin Li                         site_sysinfo.logdir.DEFAULT_EXCLUDES)
316*9c5db199SXin Li
317*9c5db199SXin Li    def test_unpickle_handle_missing__excludes_default(self):
318*9c5db199SXin Li        """Accurately handle missing _excludes attribute from pickles
319*9c5db199SXin Li
320*9c5db199SXin Li        This can happen when running brand new version of this class that
321*9c5db199SXin Li        introduced this attribute from older server side code in prod.
322*9c5db199SXin Li        """
323*9c5db199SXin Li        logdir = site_sysinfo.logdir(
324*9c5db199SXin Li                self.from_dir,
325*9c5db199SXin Li                excludes=(site_sysinfo.logdir.DEFAULT_EXCLUDES + ('a',)))
326*9c5db199SXin Li        delattr(logdir, '_excludes')
327*9c5db199SXin Li        # base_job uses protocol 2 to pickle. We follow suit.
328*9c5db199SXin Li        logdir_pickle = pickle.dumps(logdir, protocol=2)
329*9c5db199SXin Li        logdir = pickle.loads(logdir_pickle)
330*9c5db199SXin Li
331*9c5db199SXin Li        self.assertEqual(
332*9c5db199SXin Li                logdir._excludes,
333*9c5db199SXin Li                (site_sysinfo.logdir.DEFAULT_EXCLUDES + ('a',)))
334*9c5db199SXin Li
335*9c5db199SXin Li
336*9c5db199SXin Liif __name__ == '__main__':
337*9c5db199SXin Li    unittest.main()
338