1# Copyright 2020 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Test overlay.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import print_function 20 21import os 22import shutil 23import subprocess 24import tempfile 25import unittest 26from . import config 27from . import overlay 28import re 29 30 31class BindOverlayTest(unittest.TestCase): 32 33 def setUp(self): 34 self.source_dir = tempfile.mkdtemp() 35 self.destination_dir = tempfile.mkdtemp() 36 # 37 # base_dir/ 38 # base_project/ 39 # .git 40 # no_git_dir/ 41 # no_git_subdir1/ 42 # no_git_file1 43 # no_git_subdir2/ 44 # no_git_file2 45 # overlays/ 46 # unittest1/ 47 # from_dir/ 48 # .git/ 49 # upper_subdir/ 50 # lower_subdir/ 51 # from_unittest1/ 52 # .git/ 53 # from_file 54 # unittest2/ 55 # upper_subdir/ 56 # lower_subdir/ 57 # from_unittest2/ 58 # .git/ 59 # no_git_dir2/ 60 # no_git_subdir1/ 61 # no_git_subdir2/ 62 # .bindmount 63 # 64 os.mkdir(os.path.join(self.source_dir, 'base_dir')) 65 os.mkdir(os.path.join(self.source_dir, 'base_dir', 'base_project')) 66 os.mkdir(os.path.join(self.source_dir, 'base_dir', 'base_project', '.git')) 67 os.mkdir(os.path.join(self.source_dir, 'no_git_dir')) 68 os.mkdir(os.path.join(self.source_dir, 'no_git_dir', 'no_git_subdir1')) 69 open(os.path.join(self.source_dir, 70 'no_git_dir', 'no_git_subdir1', 'no_git_file1'), 'a').close() 71 os.mkdir(os.path.join(self.source_dir, 'no_git_dir', 'no_git_subdir2')) 72 open(os.path.join(self.source_dir, 73 'no_git_dir', 'no_git_subdir2', 'no_git_file2'), 'a').close() 74 os.mkdir(os.path.join(self.source_dir, 'overlays')) 75 os.mkdir(os.path.join(self.source_dir, 76 'overlays', 'unittest1')) 77 os.mkdir(os.path.join(self.source_dir, 78 'overlays', 'unittest1', 'from_dir')) 79 os.mkdir(os.path.join(self.source_dir, 80 'overlays', 'unittest1', 'from_dir', '.git')) 81 os.mkdir(os.path.join(self.source_dir, 82 'overlays', 'unittest1', 'upper_subdir')) 83 os.mkdir(os.path.join(self.source_dir, 84 'overlays', 'unittest1', 'upper_subdir', 85 'lower_subdir')) 86 os.mkdir(os.path.join(self.source_dir, 87 'overlays', 'unittest1', 'upper_subdir', 88 'lower_subdir', 'from_unittest1')) 89 os.mkdir(os.path.join(self.source_dir, 90 'overlays', 'unittest1', 'upper_subdir', 91 'lower_subdir', 'from_unittest1', '.git')) 92 os.symlink( 93 os.path.join(self.source_dir, 'overlays', 'unittest1', 94 'upper_subdir', 'lower_subdir'), 95 os.path.join(self.source_dir, 'overlays', 'unittest1', 96 'upper_subdir', 'subdir_symlink') 97 ) 98 open(os.path.join(self.source_dir, 99 'overlays', 'unittest1', 'from_file'), 'a').close() 100 os.mkdir(os.path.join(self.source_dir, 101 'overlays', 'unittest2')) 102 os.mkdir(os.path.join(self.source_dir, 103 'overlays', 'unittest2', 'upper_subdir')) 104 os.mkdir(os.path.join(self.source_dir, 105 'overlays', 'unittest2', 'upper_subdir', 106 'lower_subdir')) 107 os.mkdir(os.path.join(self.source_dir, 108 'overlays', 'unittest2', 'upper_subdir', 109 'lower_subdir', 'from_unittest2')) 110 os.mkdir(os.path.join(self.source_dir, 111 'overlays', 'unittest2', 'upper_subdir', 112 'lower_subdir', 'from_unittest2', '.git')) 113 114 os.mkdir(os.path.join(self.source_dir, 'overlays', 'no_git_dir2')) 115 os.mkdir(os.path.join(self.source_dir, 116 'overlays', 'no_git_dir2', 'no_git_subdir1')) 117 os.mkdir(os.path.join(self.source_dir, 118 'overlays', 'no_git_dir2', 'no_git_subdir2')) 119 open(os.path.join(self.source_dir, 120 'overlays', 'no_git_dir2', 'no_git_subdir2', '.bindmount'), 121 'a').close() 122 123 def tearDown(self): 124 shutil.rmtree(self.source_dir) 125 126 def testValidTargetOverlayBinds(self): 127 with tempfile.NamedTemporaryFile('w+t') as test_config: 128 test_config.write( 129 '<?xml version="1.0" encoding="UTF-8" ?>' 130 '<config>' 131 ' <target name="unittest">' 132 ' <overlay name="unittest1"/>' 133 ' <build_config>' 134 ' <goal name="goal_name"/>' 135 ' </build_config>' 136 ' </target>' 137 '</config>' 138 ) 139 test_config.flush() 140 o = overlay.BindOverlay( 141 cfg=config.factory(test_config.name), 142 build_target='unittest', 143 source_dir=self.source_dir) 144 self.assertIsNotNone(o) 145 bind_mounts = o.GetBindMounts() 146 bind_source = os.path.join(self.source_dir, 'overlays/unittest1/from_dir') 147 bind_destination = os.path.join(self.source_dir, 'from_dir') 148 self.assertEqual(bind_mounts[bind_destination], overlay.BindMount(bind_source, True, False)) 149 self.assertIn(os.path.join(self.source_dir, 'base_dir', 'base_project'), bind_mounts) 150 151 def testValidTargetOverlayBindsAllowedProjects(self): 152 with tempfile.NamedTemporaryFile('w+t') as test_config, \ 153 tempfile.NamedTemporaryFile('w+t') as test_allowed_projects: 154 test_config.write( 155 '<?xml version="1.0" encoding="UTF-8" ?>' 156 '<config>' 157 ' <target name="unittest">' 158 ' <overlay name="unittest1"/>' 159 ' <build_config allowed_projects_file="%s">' 160 ' <goal name="goal_name"/>' 161 ' </build_config>' 162 ' </target>' 163 '</config>' % test_allowed_projects.name 164 ) 165 test_config.flush() 166 test_allowed_projects.write( 167 '<?xml version="1.0" encoding="UTF-8" ?>' 168 '<manifest>' 169 ' <project name="from_dir" path="overlays/unittest1/from_dir"/>' 170 '</manifest>' 171 ) 172 test_allowed_projects.flush() 173 o = overlay.BindOverlay( 174 cfg=config.factory(test_config.name), 175 build_target='unittest', 176 source_dir=self.source_dir) 177 self.assertIsNotNone(o) 178 bind_mounts = o.GetBindMounts() 179 self.assertIn(os.path.join(self.source_dir, 'from_dir'), bind_mounts) 180 self.assertNotIn(os.path.join(self.source_dir, 'base_dir', 'base_project'), bind_mounts) 181 182 def testMultipleOverlays(self): 183 with tempfile.NamedTemporaryFile('w+t') as test_config: 184 test_config.write( 185 '<?xml version="1.0" encoding="UTF-8" ?>' 186 '<config>' 187 ' <target name="unittest">' 188 ' <overlay name="unittest1"/>' 189 ' <overlay name="unittest2"/>' 190 ' <build_config>' 191 ' <goal name="goal_name"/>' 192 ' </build_config>' 193 ' </target>' 194 '</config>' 195 ) 196 test_config.flush() 197 o = overlay.BindOverlay( 198 cfg=config.factory(test_config.name), 199 build_target='unittest', 200 source_dir=self.source_dir) 201 self.assertIsNotNone(o) 202 bind_mounts = o.GetBindMounts() 203 bind_source = os.path.join(self.source_dir, 204 'overlays/unittest1/upper_subdir/lower_subdir/from_unittest1') 205 bind_destination = os.path.join(self.source_dir, 'upper_subdir/lower_subdir/from_unittest1') 206 self.assertEqual(bind_mounts[bind_destination], overlay.BindMount(bind_source, True, False)) 207 bind_source = os.path.join(self.source_dir, 208 'overlays/unittest2/upper_subdir/lower_subdir/from_unittest2') 209 bind_destination = os.path.join(self.source_dir, 210 'upper_subdir/lower_subdir/from_unittest2') 211 self.assertEqual(bind_mounts[bind_destination], overlay.BindMount(bind_source, True, False)) 212 213 def testMultipleOverlaysWithAllowlist(self): 214 with tempfile.NamedTemporaryFile('w+t') as test_config: 215 test_config.write( 216 '<?xml version="1.0" encoding="UTF-8" ?>' 217 '<config>' 218 ' <target name="unittest">' 219 ' <overlay name="unittest1"/>' 220 ' <overlay name="unittest2"/>' 221 ' <allow_readwrite path="overlays/unittest1/upper_subdir/lower_subdir/from_unittest1"/>' 222 ' <build_config>' 223 ' <goal name="goal_name"/>' 224 ' </build_config>' 225 ' </target>' 226 '</config>' 227 ) 228 test_config.flush() 229 o = overlay.BindOverlay( 230 cfg=config.factory(test_config.name), 231 build_target='unittest', 232 source_dir=self.source_dir) 233 self.assertIsNotNone(o) 234 bind_mounts = o.GetBindMounts() 235 bind_source = os.path.join(self.source_dir, 236 'overlays/unittest1/upper_subdir/lower_subdir/from_unittest1') 237 bind_destination = os.path.join(self.source_dir, 'upper_subdir/lower_subdir/from_unittest1') 238 self.assertEqual( 239 bind_mounts[bind_destination], 240 overlay.BindMount(source_dir=bind_source, readonly=False, allows_replacement=False)) 241 bind_source = os.path.join(self.source_dir, 242 'overlays/unittest2/upper_subdir/lower_subdir/from_unittest2') 243 bind_destination = os.path.join(self.source_dir, 244 'upper_subdir/lower_subdir/from_unittest2') 245 self.assertEqual(bind_mounts[bind_destination], overlay.BindMount(bind_source, True, False)) 246 247 def testAllowReadWriteNoGitDir(self): 248 with tempfile.NamedTemporaryFile('w+t') as test_config: 249 test_config.write( 250 '<?xml version="1.0" encoding="UTF-8" ?>' 251 '<config>' 252 ' <target name="unittest">' 253 ' <overlay name="unittest1"/>' 254 ' <overlay name="unittest2"/>' 255 ' <allow_readwrite path="no_git_dir/no_git_subdir1"/>' 256 ' <build_config>' 257 ' <goal name="goal_name"/>' 258 ' </build_config>' 259 ' </target>' 260 '</config>' 261 ) 262 test_config.flush() 263 o = overlay.BindOverlay( 264 cfg=config.factory(test_config.name), 265 build_target='unittest', 266 source_dir=self.source_dir) 267 self.assertIsNotNone(o) 268 bind_mounts = o.GetBindMounts() 269 bind_source = os.path.join(self.source_dir, 270 'no_git_dir/no_git_subdir1') 271 bind_destination = os.path.join(self.source_dir, 'no_git_dir/no_git_subdir1') 272 self.assertIn(bind_destination, bind_mounts) 273 self.assertEqual( 274 bind_mounts[bind_destination], 275 overlay.BindMount(source_dir=bind_source, readonly=False, allows_replacement=False)) 276 bind_source = os.path.join(self.source_dir, 277 'no_git_dir/no_git_subdir2') 278 bind_destination = os.path.join(self.source_dir, 279 'no_git_dir/no_git_subdir2') 280 self.assertIn(bind_destination, bind_mounts) 281 self.assertEqual(bind_mounts[bind_destination], overlay.BindMount(bind_source, True, False)) 282 283 def testValidOverlaidDir(self): 284 with tempfile.NamedTemporaryFile('w+t') as test_config: 285 test_config.write( 286 '<?xml version="1.0" encoding="UTF-8" ?>' 287 '<config>' 288 ' <target name="unittest">' 289 ' <overlay name="unittest1"/>' 290 ' <build_config>' 291 ' <goal name="goal_name"/>' 292 ' </build_config>' 293 ' </target>' 294 '</config>' 295 ) 296 test_config.flush() 297 o = overlay.BindOverlay( 298 cfg=config.factory(test_config.name), 299 build_target='unittest', 300 source_dir=self.source_dir, 301 destination_dir=self.destination_dir) 302 self.assertIsNotNone(o) 303 bind_mounts = o.GetBindMounts() 304 bind_source = os.path.join(self.source_dir, 'overlays/unittest1/from_dir') 305 bind_destination = os.path.join(self.destination_dir, 'from_dir') 306 self.assertEqual(bind_mounts[bind_destination], overlay.BindMount(bind_source, True, False)) 307 308 def testValidFilesystemViewDirectoryBind(self): 309 with tempfile.NamedTemporaryFile('w+t') as test_config: 310 test_config.write( 311 '<?xml version="1.0" encoding="UTF-8" ?>' 312 '<config>' 313 ' <target name="unittest">' 314 ' <view name="unittestview"/>' 315 ' <build_config>' 316 ' <goal name="goal_name"/>' 317 ' </build_config>' 318 ' </target>' 319 ' <view name="unittestview">' 320 ' <path source="overlays/unittest1/from_dir" ' 321 ' destination="to_dir"/>' 322 ' </view>' 323 '</config>' 324 ) 325 test_config.flush() 326 o = overlay.BindOverlay( 327 cfg=config.factory(test_config.name), 328 build_target='unittest', 329 source_dir=self.source_dir) 330 self.assertIsNotNone(o) 331 bind_mounts = o.GetBindMounts() 332 bind_source = os.path.join(self.source_dir, 'overlays/unittest1/from_dir') 333 bind_destination = os.path.join(self.source_dir, 'to_dir') 334 self.assertEqual(bind_mounts[bind_destination], overlay.BindMount(bind_source, True, False)) 335 336 def testValidFilesystemViewFileBind(self): 337 with tempfile.NamedTemporaryFile('w+t') as test_config: 338 test_config.write( 339 '<?xml version="1.0" encoding="UTF-8" ?>' 340 '<config>' 341 ' <target name="unittest">' 342 ' <view name="unittestview"/>' 343 ' <build_config>' 344 ' <goal name="goal_name"/>' 345 ' </build_config>' 346 ' </target>' 347 ' <view name="unittestview">' 348 ' <path source="overlays/unittest1/from_file" ' 349 ' destination="to_file"/>' 350 ' </view>' 351 '</config>' 352 ) 353 test_config.flush() 354 o = overlay.BindOverlay( 355 cfg=config.factory(test_config.name), 356 build_target='unittest', 357 source_dir=self.source_dir) 358 self.assertIsNotNone(o) 359 bind_mounts = o.GetBindMounts() 360 bind_source = os.path.join(self.source_dir, 'overlays/unittest1/from_file') 361 bind_destination = os.path.join(self.source_dir, 'to_file') 362 self.assertEqual(bind_mounts[bind_destination], overlay.BindMount(bind_source, True, False)) 363 364 def testInvalidTarget(self): 365 with tempfile.NamedTemporaryFile('w+t') as test_config: 366 test_config.write( 367 '<?xml version="1.0" encoding="UTF-8" ?>' 368 '<config>' 369 ' <target name="unittest">' 370 ' <overlay name="unittest1"/>' 371 ' <build_config>' 372 ' <goal name="goal_name"/>' 373 ' </build_config>' 374 ' </target>' 375 '</config>' 376 ) 377 test_config.flush() 378 with self.assertRaises(KeyError): 379 overlay.BindOverlay( 380 cfg=config.factory(test_config.name), 381 build_target='unknown', 382 source_dir=self.source_dir) 383 384 def testExplicitBindMount(self): 385 with tempfile.NamedTemporaryFile('w+t') as test_config: 386 test_config.write( 387 '<?xml version="1.0" encoding="UTF-8" ?>' 388 '<config>' 389 ' <target name="target_name">' 390 ' <overlay name="no_git_dir2"/>' 391 ' <build_config>' 392 ' <goal name="goal_name"/>' 393 ' </build_config>' 394 ' </target>' 395 '</config>' 396 ) 397 test_config.flush() 398 o = overlay.BindOverlay( 399 cfg=config.factory(test_config.name), 400 build_target='target_name', 401 source_dir=self.source_dir) 402 self.assertIsNotNone(o) 403 bind_mounts = o.GetBindMounts() 404 405 bind_source = os.path.join(self.source_dir, 'overlays/no_git_dir2/no_git_subdir1') 406 bind_destination = os.path.join(self.source_dir, 'no_git_subdir1') 407 self.assertEqual(bind_mounts[bind_destination], overlay.BindMount(bind_source, True, False)) 408 409 bind_source = os.path.join(self.source_dir, 'overlays/no_git_dir2/no_git_subdir2') 410 bind_destination = os.path.join(self.source_dir, 'no_git_subdir2') 411 self.assertEqual(bind_mounts[bind_destination], overlay.BindMount(bind_source, True, False)) 412 413 def testReplacementPath(self): 414 with tempfile.NamedTemporaryFile('w+t') as test_config: 415 test_config.write( 416 '<?xml version="1.0" encoding="UTF-8" ?>' 417 '<config>' 418 ' <target name="unittest">' 419 ' <overlay name="unittest1">' 420 ' <replacement_path path="from_dir"/>' 421 ' </overlay>' 422 ' <build_config>' 423 ' <goal name="goal_name"/>' 424 ' </build_config>' 425 ' </target>' 426 '</config>' 427 ) 428 test_config.flush() 429 o = overlay.BindOverlay( 430 cfg=config.factory(test_config.name), 431 build_target='unittest', 432 source_dir=self.source_dir) 433 self.assertIsNotNone(o) 434 bind_mounts = o.GetBindMounts() 435 bind_source = os.path.join(self.source_dir, 'overlays/unittest1/from_dir') 436 bind_destination = os.path.join(self.source_dir, 'from_dir') 437 self.assertEqual(bind_mounts[bind_destination], 438 overlay.BindMount(bind_source, True, True)) 439 440if __name__ == '__main__': 441 unittest.main() 442