# Copyright 2019 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import os import tempfile import time import unittest import mock import version def _ReplaceArgs(args, *replacements): new_args = args[:] for flag, val in replacements: flag_index = args.index(flag) new_args[flag_index + 1] = val return new_args class _VersionTest(unittest.TestCase): """Unittests for the version module. """ _CHROME_VERSION_FILE = os.path.join( os.path.dirname(__file__), os.pardir, os.pardir, 'chrome', 'VERSION') _SCRIPT = os.path.join(os.path.dirname(__file__), 'version.py') _EXAMPLE_VERSION = { 'MAJOR': '74', 'MINOR': '0', 'BUILD': '3720', 'PATCH': '0', } _EXAMPLE_TEMPLATE = ( 'full = "@MAJOR@.@MINOR@.@BUILD@.@PATCH@" ' 'major = "@MAJOR@" minor = "@MINOR@" ' 'build = "@BUILD@" patch = "@PATCH@" version_id = @VERSION_ID@ ') _ANDROID_CHROME_VARS = [ 'chrome_version_code', 'monochrome_version_code', 'trichrome_version_code', 'webview_stable_version_code', 'webview_beta_version_code', 'webview_dev_version_code', ] _EXAMPLE_ANDROID_TEMPLATE = ( _EXAMPLE_TEMPLATE + ''.join( ['%s = "@%s@" ' % (el, el.upper()) for el in _ANDROID_CHROME_VARS])) _EXAMPLE_ARGS = [ '-f', _CHROME_VERSION_FILE, '-t', _EXAMPLE_TEMPLATE, ] _EXAMPLE_ANDROID_ARGS = _ReplaceArgs(_EXAMPLE_ARGS, ['-t', _EXAMPLE_ANDROID_TEMPLATE]) + [ '-a', 'arm', '--os', 'android', ] @staticmethod def _RunBuildOutput(new_version_values={}, get_new_args=lambda old_args: old_args): """Parameterized helper method for running the main testable method in version.py. Keyword arguments: new_version_values -- dict used to update _EXAMPLE_VERSION get_new_args -- lambda for updating _EXAMPLE_ANDROID_ARGS """ with mock.patch('version.FetchValuesFromFile') as \ fetch_values_from_file_mock: fetch_values_from_file_mock.side_effect = (lambda values, file : values.update( dict(_VersionTest._EXAMPLE_VERSION, **new_version_values))) new_args = get_new_args(_VersionTest._EXAMPLE_ARGS) return version.BuildOutput(new_args) def testFetchValuesFromFile(self): """It returns a dict in correct format - { : }, to verify assumption of other tests that mock this function """ result = {} version.FetchValuesFromFile(result, self._CHROME_VERSION_FILE) for key, val in result.items(): self.assertIsInstance(key, str) self.assertIsInstance(val, str) def testBuildOutputAndroid(self): """Assert it gives includes assignments of expected variables""" output = self._RunBuildOutput( get_new_args=lambda args: self._EXAMPLE_ANDROID_ARGS) contents = output['contents'] self.assertRegex(contents, r'\bchrome_version_code = "\d+"\s') self.assertRegex(contents, r'\bmonochrome_version_code = "\d+"\s') self.assertRegex(contents, r'\btrichrome_version_code = "\d+"\s') self.assertRegex(contents, r'\bwebview_stable_version_code = "\d+"\s') self.assertRegex(contents, r'\bwebview_beta_version_code = "\d+"\s') self.assertRegex(contents, r'\bwebview_dev_version_code = "\d+"\s') def testBuildOutputAndroidArchVariantsArm64(self): """Assert 64-bit-specific version codes""" new_template = ( self._EXAMPLE_ANDROID_TEMPLATE + "monochrome_64_32_version_code = \"@MONOCHROME_64_32_VERSION_CODE@\" " "monochrome_64_version_code = \"@MONOCHROME_64_VERSION_CODE@\" " "trichrome_64_32_version_code = \"@TRICHROME_64_32_VERSION_CODE@\" " "trichrome_64_version_code = \"@TRICHROME_64_VERSION_CODE@\" ") args_with_template = _ReplaceArgs(self._EXAMPLE_ANDROID_ARGS, ['-t', new_template]) new_args = _ReplaceArgs(args_with_template, ['-a', 'arm64']) output = self._RunBuildOutput(get_new_args=lambda args: new_args) contents = output['contents'] self.assertRegex(contents, r'\bmonochrome_64_32_version_code = "\d+"\s') self.assertRegex(contents, r'\bmonochrome_64_version_code = "\d+"\s') self.assertRegex(contents, r'\btrichrome_64_32_version_code = "\d+"\s') self.assertRegex(contents, r'\btrichrome_64_version_code = "\d+"\s') def testBuildOutputAndroidArchVariantsX64(self): """Assert 64-bit-specific version codes""" new_template = ( self._EXAMPLE_ANDROID_TEMPLATE + "monochrome_64_32_version_code = \"@MONOCHROME_64_32_VERSION_CODE@\" " "monochrome_64_version_code = \"@MONOCHROME_64_VERSION_CODE@\" " "trichrome_64_32_version_code = \"@TRICHROME_64_32_VERSION_CODE@\" " "trichrome_64_version_code = \"@TRICHROME_64_VERSION_CODE@\" ") args_with_template = _ReplaceArgs(self._EXAMPLE_ANDROID_ARGS, ['-t', new_template]) new_args = _ReplaceArgs(args_with_template, ['-a', 'x64']) output = self._RunBuildOutput(get_new_args=lambda args: new_args) contents = output['contents'] self.assertRegex(contents, r'\bmonochrome_64_32_version_code = "\d+"\s') self.assertRegex(contents, r'\bmonochrome_64_version_code = "\d+"\s') self.assertRegex(contents, r'\btrichrome_64_32_version_code = "\d+"\s') self.assertRegex(contents, r'\btrichrome_64_version_code = "\d+"\s') def testBuildOutputAndroidChromeArchInput(self): """Assert it raises an exception when using an invalid architecture input""" new_args = _ReplaceArgs(self._EXAMPLE_ANDROID_ARGS, ['-a', 'foobar']) # Mock sys.stderr because argparse will print to stderr when we pass # the invalid '-a' value. with self.assertRaises(SystemExit) as cm, mock.patch('sys.stderr'): self._RunBuildOutput(get_new_args=lambda args: new_args) self.assertEqual(cm.exception.code, 2) def testSetExecutable(self): """Assert that -x sets executable on POSIX and is harmless on Windows.""" with tempfile.TemporaryDirectory() as tmpdir: in_file = os.path.join(tmpdir, "in") out_file = os.path.join(tmpdir, "out") with open(in_file, "w") as f: f.write("") self.assertEqual(version.main(['-i', in_file, '-o', out_file, '-x']), 0) # Whether lstat(out_file).st_mode has the executable bits set is # platform-specific. Therefore, test that out_file has the same # permissions that in_file would have after chmod(in_file, 0o755). # On Windows: both files will have 0o666. # On POSIX: both files will have 0o755. os.chmod(in_file, 0o755) # On Windows, this sets in_file to 0o666. self.assertEqual(os.lstat(in_file).st_mode, os.lstat(out_file).st_mode) def testWriteIfChangedUpdateWhenContentChanged(self): """Assert it updates mtime of file when content is changed.""" with tempfile.TemporaryDirectory() as tmpdir: file_name = os.path.join(tmpdir, "version.h") old_contents = "old contents" with open(file_name, "w") as f: f.write(old_contents) os.chmod(file_name, 0o644) mtime = os.lstat(file_name).st_mtime time.sleep(0.1) contents = "new contents" version.WriteIfChanged(file_name, contents, 0o644) with open(file_name) as f: self.assertEqual(contents, f.read()) self.assertNotEqual(mtime, os.lstat(file_name).st_mtime) def testWriteIfChangedUpdateWhenModeChanged(self): """Assert it updates mtime of file when mode is changed.""" with tempfile.TemporaryDirectory() as tmpdir: file_name = os.path.join(tmpdir, "version.h") contents = "old contents" with open(file_name, "w") as f: f.write(contents) os.chmod(file_name, 0o644) mtime = os.lstat(file_name).st_mtime time.sleep(0.1) version.WriteIfChanged(file_name, contents, 0o755) with open(file_name) as f: self.assertEqual(contents, f.read()) self.assertNotEqual(mtime, os.lstat(file_name).st_mtime) def testWriteIfChangedNoUpdate(self): """Assert it does not update mtime of file when nothing is changed.""" with tempfile.TemporaryDirectory() as tmpdir: file_name = os.path.join(tmpdir, "version.h") contents = "old contents" with open(file_name, "w") as f: f.write(contents) os.chmod(file_name, 0o644) mtime = os.lstat(file_name).st_mtime time.sleep(0.1) version.WriteIfChanged(file_name, contents, 0o644) with open(file_name) as f: self.assertEqual(contents, f.read()) self.assertEqual(mtime, os.lstat(file_name).st_mtime) if __name__ == '__main__': unittest.main()