xref: /aosp_15_r20/external/cronet/android/tools/license/tests/license_converter_test.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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