Commit 1773f37d authored by Edward Lemur's avatar Edward Lemur Committed by LUCI CQ

gclient_utils: Add temporary_file method.

Useful when passing information to a subcommand via a temporary file.

Bug: 1051631
Change-Id: I0b8deda921effd24a37109544e1e7ca22e00ba4e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/2068942Reviewed-by: 's avatarJosip Sokcevic <sokcevic@google.com>
Commit-Queue: Edward Lesmes <ehmaldonado@chromium.org>
parent 8410164a
...@@ -170,8 +170,8 @@ def FileRead(filename, mode='rbU'): ...@@ -170,8 +170,8 @@ def FileRead(filename, mode='rbU'):
return s return s
def FileWrite(filename, content, mode='w'): def FileWrite(filename, content, mode='w', encoding='utf-8'):
with codecs.open(filename, mode=mode, encoding='utf-8') as f: with codecs.open(filename, mode=mode, encoding=encoding) as f:
f.write(content) f.write(content)
...@@ -185,6 +185,35 @@ def temporary_directory(**kwargs): ...@@ -185,6 +185,35 @@ def temporary_directory(**kwargs):
rmtree(tdir) rmtree(tdir)
@contextlib.contextmanager
def temporary_file():
"""Creates a temporary file.
On Windows, a file must be closed before it can be opened again. This function
allows to write something like:
with gclient_utils.temporary_file() as tmp:
gclient_utils.FileWrite(tmp, foo)
useful_stuff(tmp)
Instead of something like:
with tempfile.NamedTemporaryFile(delete=False) as tmp:
tmp.write(foo)
tmp.close()
try:
useful_stuff(tmp)
finally:
os.remove(tmp.name)
"""
handle, name = tempfile.mkstemp()
os.close(handle)
try:
yield name
finally:
os.remove(name)
def safe_rename(old, new): def safe_rename(old, new):
"""Renames a file reliably. """Renames a file reliably.
......
...@@ -2342,12 +2342,10 @@ class Changelist(object): ...@@ -2342,12 +2342,10 @@ class Changelist(object):
parent = self._ComputeParent(remote, upstream_branch, custom_cl_base, parent = self._ComputeParent(remote, upstream_branch, custom_cl_base,
options.force, change_desc) options.force, change_desc)
tree = RunGit(['rev-parse', 'HEAD:']).strip() tree = RunGit(['rev-parse', 'HEAD:']).strip()
with tempfile.NamedTemporaryFile(delete=False) as desc_tempfile: with gclient_utils.temporary_file() as desc_tempfile:
desc_tempfile.write(change_desc.description.encode('utf-8', 'replace')) gclient_utils.FileWrite(desc_tempfile, change_desc.description)
desc_tempfile.close() ref_to_push = RunGit(
ref_to_push = RunGit(['commit-tree', tree, '-p', parent, ['commit-tree', tree, '-p', parent, '-F', desc_tempfile]).strip()
'-F', desc_tempfile.name]).strip()
os.remove(desc_tempfile.name)
else: # if not options.squash else: # if not options.squash
change_desc = ChangeDescription( change_desc = ChangeDescription(
options.message or _create_description_from_log(git_diff_args)) options.message or _create_description_from_log(git_diff_args))
......
...@@ -15,6 +15,7 @@ import subprocess2 ...@@ -15,6 +15,7 @@ import subprocess2
import sys import sys
import tempfile import tempfile
import gclient_utils
import git_footers import git_footers
import owners import owners
import owners_finder import owners_finder
...@@ -119,13 +120,11 @@ def UploadCl(cl_index, num_cls, refactor_branch, refactor_branch_upstream, ...@@ -119,13 +120,11 @@ def UploadCl(cl_index, num_cls, refactor_branch, refactor_branch_upstream,
# Commit changes. The temporary file is created with delete=False so that it # Commit changes. The temporary file is created with delete=False so that it
# can be deleted manually after git has read it rather than automatically # can be deleted manually after git has read it rather than automatically
# when it is closed. # when it is closed.
with tempfile.NamedTemporaryFile(delete=False) as tmp_file: with gclient_utils.temporary_file() as tmp_file:
tmp_file.write( gclient_utils.FileWrite(
tmp_file,
FormatDescriptionOrComment(description, directory, cl_index, num_cls)) FormatDescriptionOrComment(description, directory, cl_index, num_cls))
# Close the file to let git open it at the next line. git.run('commit', '-F', tmp_file)
tmp_file.close()
git.run('commit', '-F', tmp_file.name)
os.remove(tmp_file.name)
# Upload a CL. # Upload a CL.
upload_args = ['-f', '-r', reviewer] upload_args = ['-f', '-r', reviewer]
......
...@@ -337,24 +337,21 @@ class GClientUtilsTest(trial_dir.TestCase): ...@@ -337,24 +337,21 @@ class GClientUtilsTest(trial_dir.TestCase):
expected, gclient_utils.ParseCodereviewSettingsContent(content)) expected, gclient_utils.ParseCodereviewSettingsContent(content))
def testFileRead_Bytes(self): def testFileRead_Bytes(self):
with tempfile.NamedTemporaryFile(delete=False) as tmp: with gclient_utils.temporary_file() as tmp:
tmp.write(b'foo \xe2\x9c bar') gclient_utils.FileWrite(
# NamedTemporaryFiles must be closed on Windows before being opened again. tmp, b'foo \xe2\x9c bar', mode='wb', encoding=None)
tmp.close() self.assertEqual('foo \ufffd bar', gclient_utils.FileRead(tmp))
try:
self.assertEqual('foo \ufffd bar', gclient_utils.FileRead(tmp.name))
finally:
os.remove(tmp.name)
def testFileRead_Unicode(self): def testFileRead_Unicode(self):
with tempfile.NamedTemporaryFile(delete=False) as tmp: with gclient_utils.temporary_file() as tmp:
tmp.write(b'foo \xe2\x9c\x94 bar') gclient_utils.FileWrite(tmp, 'foo ✔ bar')
# NamedTemporaryFiles must be closed on Windows before being opened again. self.assertEqual('foo ✔ bar', gclient_utils.FileRead(tmp))
tmp.close()
try: def testTemporaryFile(self):
self.assertEqual('foo ✔ bar', gclient_utils.FileRead(tmp.name)) with gclient_utils.temporary_file() as tmp:
finally: gclient_utils.FileWrite(tmp, 'test')
os.remove(tmp.name) self.assertEqual('test', gclient_utils.FileRead(tmp))
self.assertFalse(os.path.exists(tmp))
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -32,7 +32,9 @@ import metrics ...@@ -32,7 +32,9 @@ import metrics
# We have to disable monitoring before importing git_cl. # We have to disable monitoring before importing git_cl.
metrics.DISABLE_METRICS_COLLECTION = True metrics.DISABLE_METRICS_COLLECTION = True
import contextlib
import clang_format import clang_format
import gclient_utils
import gerrit_util import gerrit_util
import git_cl import git_cl
import git_common import git_common
...@@ -55,26 +57,14 @@ def _constantFn(return_value): ...@@ -55,26 +57,14 @@ def _constantFn(return_value):
CERR1 = callError(1) CERR1 = callError(1)
def MakeNamedTemporaryFileMock(test, expected_content): class TemporaryFileMock(object):
class NamedTemporaryFileMock(object): def __init__(self):
def __init__(self, *args, **kwargs): self.suffix = 0
self.name = '/tmp/named'
self.expected_content = expected_content.encode('utf-8', 'replace')
def __enter__(self):
return self
def __exit__(self, _type, _value, _tb):
pass
def write(self, content):
if self.expected_content:
test.assertEqual(self.expected_content, content)
def close(self):
pass
return NamedTemporaryFileMock @contextlib.contextmanager
def __call__(self):
self.suffix += 1
yield '/tmp/fake-temp' + str(self.suffix)
class ChangelistMock(object): class ChangelistMock(object):
...@@ -897,8 +887,9 @@ class TestGitCl(unittest.TestCase): ...@@ -897,8 +887,9 @@ class TestGitCl(unittest.TestCase):
calls += [ calls += [
((['git', 'rev-parse', 'HEAD:'],), # `HEAD:` means HEAD's tree hash. ((['git', 'rev-parse', 'HEAD:'],), # `HEAD:` means HEAD's tree hash.
'0123456789abcdef'), '0123456789abcdef'),
((['FileWrite', '/tmp/fake-temp1', description],), None),
((['git', 'commit-tree', '0123456789abcdef', '-p', parent, ((['git', 'commit-tree', '0123456789abcdef', '-p', parent,
'-F', '/tmp/named'],), '-F', '/tmp/fake-temp1'],),
ref_to_push), ref_to_push),
] ]
else: else:
...@@ -1178,9 +1169,7 @@ class TestGitCl(unittest.TestCase): ...@@ -1178,9 +1169,7 @@ class TestGitCl(unittest.TestCase):
change_id=change_id) change_id=change_id)
if fetched_status != 'ABANDONED': if fetched_status != 'ABANDONED':
mock.patch( mock.patch(
'tempfile.NamedTemporaryFile', 'gclient_utils.temporary_file', TemporaryFileMock()).start()
MakeNamedTemporaryFileMock(
self, expected_content=description)).start()
mock.patch('os.remove', return_value=True).start() mock.patch('os.remove', return_value=True).start()
self.calls += self._gerrit_upload_calls( self.calls += self._gerrit_upload_calls(
description, reviewers, squash, description, reviewers, squash,
......
...@@ -17,6 +17,7 @@ else: ...@@ -17,6 +17,7 @@ else:
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import gclient_utils
import git_footers import git_footers
class GitFootersTest(unittest.TestCase): class GitFootersTest(unittest.TestCase):
...@@ -256,15 +257,10 @@ My commit message is my best friend. It is my life. I must master it. ...@@ -256,15 +257,10 @@ My commit message is my best friend. It is my life. I must master it.
'sys.stdin', 'sys.stdin',
StringIO('line\r\nany spaces\r\n\r\n\r\nFoo: 1\nBar: 2\nFoo: 3')) StringIO('line\r\nany spaces\r\n\r\n\r\nFoo: 1\nBar: 2\nFoo: 3'))
def testToJson(self): def testToJson(self):
with tempfile.NamedTemporaryFile(delete=False) as tmp: with gclient_utils.temporary_file() as tmp:
try: self.assertEqual(git_footers.main(['--json', tmp]), 0)
# NamedTemporaryFiles must be closed on Windows before being opened with open(tmp) as f:
# again. js = json.load(f)
tmp.close()
self.assertEqual(git_footers.main(['--json', tmp.name]), 0)
js = json.load(open(tmp.name))
finally:
os.remove(tmp.name)
self.assertEqual(js, {'Foo': ['3', '1'], 'Bar': ['2']}) self.assertEqual(js, {'Foo': ['3', '1'], 'Bar': ['2']})
......
...@@ -28,6 +28,7 @@ sys.path.insert(0, DEPOT_TOOLS_ROOT) ...@@ -28,6 +28,7 @@ sys.path.insert(0, DEPOT_TOOLS_ROOT)
from testing_support import coverage_utils from testing_support import coverage_utils
from testing_support import git_test_utils from testing_support import git_test_utils
import gclient_utils
import git_common import git_common
GitRepo = git_test_utils.GitRepo GitRepo = git_test_utils.GitRepo
...@@ -186,16 +187,16 @@ class GitHyperBlameMainTest(GitHyperBlameTestBase): ...@@ -186,16 +187,16 @@ class GitHyperBlameMainTest(GitHyperBlameTestBase):
self.blame_line('A', '2*) line 2.1')] self.blame_line('A', '2*) line 2.1')]
outbuf = BytesIO() outbuf = BytesIO()
with tempfile.NamedTemporaryFile(mode='w+', prefix='ignore') as ignore_file: with gclient_utils.temporary_file() as ignore_file:
ignore_file.write('# Line comments are allowed.\n') gclient_utils.FileWrite(
ignore_file.write('\n') ignore_file,
ignore_file.write('{}\n'.format(self.repo['B'])) '# Line comments are allowed.\n'
# A revision that is not in the repo (should be ignored). '\n'
ignore_file.write('xxxx\n') '{}\n'
ignore_file.flush() 'xxxx\n'.format(self.repo['B']))
retval = self.repo.run( retval = self.repo.run(
self.git_hyper_blame.main, self.git_hyper_blame.main,
['--ignore-file', ignore_file.name, 'tag_C', 'some/files/file'], ['--ignore-file', ignore_file, 'tag_C', 'some/files/file'],
outbuf) outbuf)
self.assertEqual(0, retval) self.assertEqual(0, retval)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment