lockfile_test.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. #!/usr/bin/env vpython3
  2. # Copyright 2020 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. """Unit tests for lockfile.py"""
  6. import logging
  7. import os
  8. import shutil
  9. import sys
  10. import tempfile
  11. import threading
  12. import unittest
  13. if sys.version_info.major == 2:
  14. import mock
  15. import Queue
  16. else:
  17. from unittest import mock
  18. import queue as Queue
  19. DEPOT_TOOLS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  20. sys.path.insert(0, DEPOT_TOOLS_ROOT)
  21. from testing_support import coverage_utils
  22. import lockfile
  23. class LockTest(unittest.TestCase):
  24. def setUp(self):
  25. self.cache_dir = tempfile.mkdtemp(prefix='lockfile')
  26. self.addCleanup(shutil.rmtree, self.cache_dir, ignore_errors=True)
  27. def testLock(self):
  28. with lockfile.lock(self.cache_dir):
  29. # cached dir locked, attempt to lock it again
  30. with self.assertRaises(lockfile.LockError):
  31. with lockfile.lock(self.cache_dir):
  32. pass
  33. with lockfile.lock(self.cache_dir):
  34. pass
  35. @mock.patch('time.sleep')
  36. def testLockConcurrent(self, sleep_mock):
  37. '''testLockConcurrent simulates what happens when two separate processes try
  38. to acquire the same file lock with timeout.'''
  39. # Queues q_f1 and q_sleep are used to controll execution of individual
  40. # threads.
  41. q_f1 = Queue.Queue()
  42. q_sleep = Queue.Queue()
  43. results = Queue.Queue()
  44. def side_effect(arg):
  45. '''side_effect is called when with l.lock is blocked. In this unit test
  46. case, it comes from f2.'''
  47. logging.debug('sleep: started')
  48. q_sleep.put(True)
  49. logging.debug('sleep: waiting for q_sleep to be consumed')
  50. q_sleep.join()
  51. logging.debug('sleep: waiting for result before exiting')
  52. results.get(timeout=1)
  53. logging.debug('sleep: exiting')
  54. sleep_mock.side_effect = side_effect
  55. def f1():
  56. '''f1 enters first in l.lock (controlled via q_f1). It then waits for
  57. side_effect to put a message in queue q_sleep.'''
  58. logging.debug('f1 started, locking')
  59. with lockfile.lock(self.cache_dir, timeout=1):
  60. logging.debug('f1: locked')
  61. q_f1.put(True)
  62. logging.debug('f1: waiting on q_f1 to be consumed')
  63. q_f1.join()
  64. logging.debug('f1: done waiting on q_f1, getting q_sleep')
  65. q_sleep.get(timeout=1)
  66. results.put(True)
  67. logging.debug('f1: lock released')
  68. q_sleep.task_done()
  69. logging.debug('f1: exiting')
  70. def f2():
  71. '''f2 enters second in l.lock (controlled by q_f1).'''
  72. logging.debug('f2: started, consuming q_f1')
  73. q_f1.get(timeout=1) # wait for f1 to execute lock
  74. q_f1.task_done()
  75. logging.debug('f2: done waiting for q_f1, locking')
  76. with lockfile.lock(self.cache_dir, timeout=1):
  77. logging.debug('f2: locked')
  78. results.put(True)
  79. t1 = threading.Thread(target=f1)
  80. t1.start()
  81. t2 = threading.Thread(target=f2)
  82. t2.start()
  83. t1.join()
  84. t2.join()
  85. # One result was consumed by side_effect, we expect only one in the queue.
  86. self.assertEqual(1, results.qsize())
  87. sleep_mock.assert_called_once_with(1)
  88. if __name__ == '__main__':
  89. logging.basicConfig(
  90. level=logging.DEBUG if '-v' in sys.argv else logging.ERROR)
  91. sys.exit(
  92. coverage_utils.covered_main(
  93. (os.path.join(DEPOT_TOOLS_ROOT, 'git_cache.py')),
  94. required_percentage=0))