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