1import unittest 2import tempfile 3from pathlib import Path 4import os 5import sys 6import glob 7 8PARENT_ROOT = os.path.abspath( 9 os.path.join(os.path.dirname(__file__), os.pardir)) 10 11sys.path.insert(0, PARENT_ROOT) 12import license_type, license_utils, \ 13 create_android_metadata_license, mapper, constants 14 15 16class LicenseParserTest(unittest.TestCase): 17 def _write_readme_file(self, content: str): 18 with open(self.temp_file.name, 'w') as file: 19 file.write(content) 20 21 return self.temp_file 22 23 def setUp(self): 24 self.temp_file = tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8", 25 delete=False) 26 27 def tearDown(self): 28 os.remove(self.temp_file.name) 29 30 def test_readme_chromium_parsing_correct(self): 31 temp_file = self._write_readme_file("\n".join([ 32 "Name: some_name", 33 "URL: some_site", 34 "License: Apache 2.0", 35 "License File: LICENSE" 36 ])) 37 metadata = license_utils.parse_chromium_readme_file(temp_file.name, 38 lambda x: x) 39 self.assertEqual(metadata.get_licenses(), ["Apache 2.0"]) 40 self.assertEqual(metadata.get_name(), "some_name") 41 self.assertEqual(metadata.get_url(), "some_site") 42 self.assertEqual(metadata.get_license_file_path(), "LICENSE") 43 44 def test_readme_chromium_parsing_post_process(self): 45 temp_file = self._write_readme_file("\n".join([ 46 "Name: some_name", 47 "URL: some_site", 48 "License: Apache 2.0", 49 "License File: LICENSE" 50 ])) 51 52 metadata = license_utils.parse_chromium_readme_file(temp_file.name, 53 constants.create_license_post_processing( 54 mapper.Mapper( 55 "License", 56 ["Apache 2.0"], 57 ["MPL 1.1"]))) 58 self.assertEqual(metadata.get_licenses(), ["MPL 1.1"]) 59 self.assertEqual(metadata.get_name(), "some_name") 60 self.assertEqual(metadata.get_url(), "some_site") 61 self.assertEqual(metadata.get_license_file_path(), "LICENSE") 62 63 def test_readme_chromium_parsing_post_process_expected_check_fails(self): 64 temp_file = self._write_readme_file("\n".join([ 65 "Name: some_name", 66 "URL: some_site", 67 "License: Apache 2.0", 68 "License File: LICENSE" 69 ])) 70 71 self.assertRaisesRegex(Exception, 72 "Failed to post-process", 73 lambda: license_utils.parse_chromium_readme_file( 74 temp_file.name, 75 constants.create_license_post_processing( 76 mapper.Mapper("License", ["Apache 2.1"], 77 ["MPL 1.1"])))) 78 79 def test_readme_chromium_parsing_post_process_expected_check_fails_should_raise( 80 self): 81 temp_file = self._write_readme_file("\n".join([ 82 "Name: some_name", 83 "URL: some_site", 84 "License: Apache 2.0", 85 "License File: LICENSE" 86 ])) 87 88 self.assertRaisesRegex(Exception, 89 "Failed to post-process", 90 lambda: license_utils.parse_chromium_readme_file( 91 temp_file.name, 92 constants.create_license_post_processing( 93 mapper.Mapper("License", None, 94 ["MPL 1.1"])))) 95 96 def test_readme_chromium_parsing_post_process_expected_check_with_no_key_fails_should_raise( 97 self): 98 temp_file = self._write_readme_file("\n".join([ 99 "Name: some_name", 100 "URL: some_site", 101 "License File: LICENSE" 102 ])) 103 104 self.assertRaisesRegex(Exception, 105 "Failed to post-process", 106 lambda: license_utils.parse_chromium_readme_file( 107 temp_file.name, 108 constants.create_license_post_processing( 109 mapper.Mapper("License", "Apache 2.0", 110 ["MPL 1.1"])))) 111 112 def test_readme_chromium_parsing_unidentified_license(self): 113 temp_file = self._write_readme_file("\n".join([ 114 "Name: some_name", 115 "URL: some_site", 116 "License: FOO_BAR", 117 "License File: LICENSE" 118 ])) 119 self.assertRaisesRegex(license_utils.InvalidMetadata, 120 "contains unidentified license \"FOO_BAR\"", 121 lambda: license_utils.parse_chromium_readme_file( 122 temp_file.name, 123 lambda x: x)) 124 125 def test_readme_chromium_parsing_no_license_file(self): 126 temp_file = self._write_readme_file("\n".join([ 127 "Name: some_name", 128 "URL: some_site", 129 "License: Apache 2.0", 130 ])) 131 self.assertRaisesRegex(license_utils.InvalidMetadata, 132 "License file path not declared in", 133 lambda: license_utils.parse_chromium_readme_file( 134 temp_file.name, 135 lambda x: x)) 136 137 def test_readme_chromium_parsing_malformed(self): 138 temp_file = self._write_readme_file("") 139 140 self.assertRaisesRegex(Exception, "Failed to parse any valid metadata", 141 lambda: license_utils.parse_chromium_readme_file( 142 temp_file.name, 143 lambda x: x)) 144 145 def test_most_restrictive_license_comparison(self): 146 self.assertEqual( 147 license_utils.get_most_restrictive_type(["Apache 2.0", "MPL 1.1"]), 148 license_type.LicenseType.RECIPROCAL) 149 150 def test_generate_license_for_rust_crate(self): 151 with tempfile.TemporaryDirectory() as temp_directory: 152 # Rust directories in Chromium have a special structure that looks like 153 # //third_party/rust 154 # /my_crate/ 155 # /crates/my_crate/src 156 # README.chromium and BUILD.gn lives in //third_party/rust/my_crate while 157 # the actual crate lives in //third_party/rust/crates/my_crate/. 158 # 159 # This test verifies that we generate the licensing files in the directory 160 # where the source lives and not where the README.chromium lives. 161 # 162 # See https://source.chromium.org/chromium/chromium/src/+/main:third_party/rust/ 163 readme_dir = Path( 164 os.path.join(temp_directory, "third_party/rust/my_crate")) 165 readme_dir.mkdir(parents=True) 166 crate_path = Path( 167 os.path.join(temp_directory, "third_party/rust/crates/my_crate/src")) 168 crate_path.mkdir(parents=True) 169 self._write_empty_file(os.path.join(crate_path, "COPYING")) 170 with open(os.path.join(readme_dir, "README.chromium"), 171 "w", encoding="utf-8") as readme: 172 readme.write("\n".join([ 173 "Name: some_name", 174 "URL: some_site", 175 "License: Apache 2.0", 176 "License File: //third_party/rust/crates/my_crate/src/COPYING" 177 ])) 178 # COPYING file must exist as we check for symlink to make sure that 179 # they still exist. 180 create_android_metadata_license.update_license(temp_directory, {}) 181 self.assertTrue(os.path.exists(os.path.join(crate_path, "METADATA"))) 182 self.assertTrue(os.path.islink(os.path.join(crate_path, "LICENSE"))) 183 self.assertTrue(os.path.exists( 184 os.path.join(crate_path, "MODULE_LICENSE_APACHE_2_0"))) 185 metadata_content = Path( 186 os.path.join(crate_path, "METADATA")).read_text() 187 self.assertRegex( 188 metadata_content, 189 "name: \"some_name\"") 190 self.assertRegex( 191 metadata_content, 192 "license_type: NOTICE") 193 # Verify that the symlink is relative and not absolute path. 194 self.assertRegex(os.readlink(os.path.join(crate_path, "LICENSE")), 195 "^[^\/].*", 196 "Symlink path must be relative.") 197 198 def test_generate_license_for_temp_dir(self): 199 with tempfile.TemporaryDirectory() as temp_directory: 200 with open(os.path.join(temp_directory, "README.chromium"), 201 "w", encoding="utf-8") as readme: 202 readme.write("\n".join([ 203 "Name: some_name", 204 "URL: some_site", 205 "License: Apache 2.0", 206 "License File: COPYING" 207 ])) 208 # COPYING file must exist as we check for symlink to make sure that 209 # they still exist. 210 self._write_empty_file(os.path.join(temp_directory, "COPYING")) 211 create_android_metadata_license.update_license(temp_directory, {}) 212 self.assertTrue( 213 os.path.exists(os.path.join(temp_directory, "METADATA"))) 214 self.assertTrue(os.path.islink(os.path.join(temp_directory, "LICENSE"))) 215 self.assertTrue(os.path.exists( 216 os.path.join(temp_directory, "MODULE_LICENSE_APACHE_2_0"))) 217 metadata_content = Path( 218 os.path.join(temp_directory, "METADATA")).read_text() 219 self.assertRegex( 220 metadata_content, 221 "name: \"some_name\"") 222 self.assertRegex( 223 metadata_content, 224 "license_type: NOTICE") 225 226 def test_verify_only_mode_missing_metadata(self): 227 with tempfile.TemporaryDirectory() as temp_directory: 228 with open(os.path.join(temp_directory, "README.chromium"), 229 "w", encoding="utf-8") as readme: 230 readme.write("\n".join([ 231 "Name: some_name", 232 "URL: some_site", 233 "License: Apache 2.0", 234 "License File: COPYING" 235 ])) 236 # COPYING file must exist as we check for symlink to make sure that 237 # they still exist. 238 self._write_empty_file(os.path.join(temp_directory, "COPYING")) 239 create_android_metadata_license.update_license(temp_directory, {}) 240 self.assertTrue( 241 os.path.exists(os.path.join(temp_directory, "METADATA"))) 242 os.remove(os.path.join(temp_directory, "METADATA")) 243 self.assertRaisesRegex(Exception, "Failed to find metadata", 244 lambda: create_android_metadata_license.update_license( 245 temp_directory, {}, 246 verify_only=True)) 247 248 def _write_empty_file(self, path: str): 249 Path(path).touch() 250 251 def test_verify_only_mode_content_mismatch(self): 252 with tempfile.TemporaryDirectory() as temp_directory: 253 with open(os.path.join(temp_directory, "README.chromium"), 254 "w", encoding="utf-8") as readme: 255 readme.write("\n".join([ 256 "Name: some_name", 257 "URL: some_site", 258 "License: Apache 2.0", 259 "License File: COPYING" 260 ])) 261 # COPYING file must exist as we check for symlink to make sure that 262 # they still exist. 263 self._write_empty_file(os.path.join(temp_directory, "COPYING")) 264 create_android_metadata_license.update_license(temp_directory, {}) 265 metadata_path = os.path.join(temp_directory, "METADATA") 266 self.assertTrue(os.path.exists(metadata_path)) 267 # The METADATA header must exist otherwise it will be considered a 268 # manually written file. 269 Path(metadata_path).write_text("\n".join( 270 [create_android_metadata_license.METADATA_HEADER, 271 "some_random_text"]), encoding="utf-8") 272 self.assertRaisesRegex(Exception, 273 "Please re-run create_android_metadata_license.py", 274 lambda: create_android_metadata_license.update_license( 275 temp_directory, {}, 276 verify_only=True)) 277 278 def test_verify_only_mode_missing_license(self): 279 with tempfile.TemporaryDirectory() as temp_directory: 280 with open(os.path.join(temp_directory, "README.chromium"), 281 "w", encoding="utf-8") as readme: 282 readme.write("\n".join([ 283 "Name: some_name", 284 "URL: some_site", 285 "License: Apache 2.0", 286 "License File: COPYING" 287 ])) 288 create_android_metadata_license.update_license(temp_directory, {}) 289 self.assertTrue(os.path.islink(os.path.join(temp_directory, "LICENSE"))) 290 os.remove(os.path.join(temp_directory, "LICENSE")) 291 self.assertRaisesRegex(Exception, "License symlink does not exist", 292 lambda: create_android_metadata_license.update_license( 293 temp_directory, {}, 294 verify_only=True)) 295 296 def test_verify_only_mode_bad_symlink(self): 297 with tempfile.TemporaryDirectory() as temp_directory: 298 with open(os.path.join(temp_directory, "README.chromium"), 299 "w", encoding="utf-8") as readme: 300 readme.write("\n".join([ 301 "Name: some_name", 302 "URL: some_site", 303 "License: Apache 2.0", 304 "License File: COPYING" 305 ])) 306 create_android_metadata_license.update_license(temp_directory, {}) 307 self.assertTrue(os.path.islink(os.path.join(temp_directory, "LICENSE"))) 308 self.assertRaisesRegex(Exception, "License symlink does not exist", 309 lambda: create_android_metadata_license.update_license( 310 temp_directory, {}, 311 verify_only=True)) 312 313 def test_verify_only_mode_missing_module(self): 314 with tempfile.TemporaryDirectory() as temp_directory: 315 with open(os.path.join(temp_directory, "README.chromium"), 316 "w", encoding="utf-8") as readme: 317 readme.write("\n".join([ 318 "Name: some_name", 319 "URL: some_site", 320 "License: Apache 2.0", 321 "License File: COPYING" 322 ])) 323 # COPYING file must exist as we check for symlink to make sure that 324 # they still exist. 325 self._write_empty_file(os.path.join(temp_directory, "COPYING")) 326 create_android_metadata_license.update_license(temp_directory, {}) 327 self.assertTrue(os.path.exists( 328 os.path.join(temp_directory, "MODULE_LICENSE_APACHE_2_0"))) 329 os.remove(os.path.join(temp_directory, "MODULE_LICENSE_APACHE_2_0")) 330 self.assertRaisesRegex(Exception, "Failed to find module file", 331 lambda: create_android_metadata_license.update_license( 332 temp_directory, {}, 333 verify_only=True)) 334 335 @unittest.skip("b/372449684") 336 def test_license_for_aosp(self): 337 """This test verifies that external/cronet conforms to the licensing structure.""" 338 # When running inside the context of atest, the working directory contains 339 # all the METADATA / README.chromium / LICENSE, etc.. files that we need. 340 # hence why repo_path=os.getcwd() 341 try: 342 create_android_metadata_license.update_license(repo_path=os.getcwd(), 343 verify_only=True) 344 except Exception: 345 self.fail( 346 "Please update the licensing by running create_android_metadata_license.py") 347 348 349if __name__ == '__main__': 350 unittest.main() 351