| # Copyright (C) 2024 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Tests for init_ddk.py""" |
| |
| import json |
| import logging |
| import pathlib |
| import shutil |
| import tempfile |
| import textwrap |
| from typing import Any |
| import xml.dom.minidom |
| |
| from absl.testing import absltest |
| from absl.testing import parameterized |
| from repo_wrapper import ProjectAbsState, ProjectSyncStates |
| import init_ddk |
| |
| # pylint: disable=protected-access |
| |
| |
| def join(*args: Any) -> str: |
| return "\n".join([*args]) |
| |
| |
| _HELLO_WORLD = "Hello World!" |
| |
| |
| class KleafProjectSetterTest(parameterized.TestCase): |
| |
| @parameterized.named_parameters([ |
| ( |
| "Empty", |
| "", |
| join( |
| init_ddk._FILE_MARKER_BEGIN, |
| _HELLO_WORLD, |
| init_ddk._FILE_MARKER_END, |
| ), |
| ), |
| ( |
| "BeforeNoMarkers", |
| "Existing test\n", |
| join( |
| "Existing test", |
| init_ddk._FILE_MARKER_BEGIN, |
| _HELLO_WORLD, |
| init_ddk._FILE_MARKER_END, |
| ), |
| ), |
| ( |
| "AfterMarkers", |
| join( |
| init_ddk._FILE_MARKER_BEGIN, |
| init_ddk._FILE_MARKER_END, |
| "Existing test after.", |
| ), |
| join( |
| init_ddk._FILE_MARKER_BEGIN, |
| _HELLO_WORLD, |
| init_ddk._FILE_MARKER_END, |
| "Existing test after.", |
| ), |
| ), |
| ]) |
| def test_update_file_existing(self, current_content, wanted_content): |
| """Tests only text within markers are updated.""" |
| with tempfile.TemporaryDirectory() as tmp: |
| tmp_file = pathlib.Path(tmp) / "some_file" |
| with open(tmp_file, "w+", encoding="utf-8") as tf: |
| tf.write(current_content) |
| init_ddk.KleafProjectSetter._update_file( |
| tmp_file, "\n" + _HELLO_WORLD |
| ) |
| with open(tmp_file, "r", encoding="utf-8") as got: |
| self.assertEqual(wanted_content, got.read()) |
| |
| def test_update_file_no_existing(self): |
| """Tests files are created when they don't exist.""" |
| with tempfile.TemporaryDirectory() as tmp: |
| tmp_file = pathlib.Path(tmp) / "some_file" |
| init_ddk.KleafProjectSetter._update_file( |
| tmp_file, "\n" + _HELLO_WORLD |
| ) |
| with open(tmp_file, "r", encoding="utf-8") as got: |
| self.assertEqual( |
| join( |
| init_ddk._FILE_MARKER_BEGIN, |
| _HELLO_WORLD, |
| init_ddk._FILE_MARKER_END, |
| ), |
| got.read(), |
| ) |
| |
| def test_update_file_symlink(self): |
| """Tests updating a file does not remove the symlink.""" |
| with tempfile.TemporaryDirectory() as tmp: |
| tmp_file = pathlib.Path(tmp) / "some_file" |
| tmp_file.write_text("Hello world!") |
| tmp_link = pathlib.Path(tmp) / "some_link" |
| tmp_link.symlink_to(tmp_file) |
| init_ddk.KleafProjectSetter._update_file(tmp_link, "\n") |
| self.assertTrue(tmp_file.exists()) |
| self.assertTrue(tmp_link.exists()) |
| self.assertTrue(tmp_link.is_symlink()) |
| self.assertEqual(tmp_link.resolve(), tmp_file.resolve()) |
| |
| def test_relevant_directories_created(self): |
| """Tests corresponding directories are created if they don't exist.""" |
| with tempfile.TemporaryDirectory() as temp_dir: |
| temp_dir = pathlib.Path(temp_dir) |
| ddk_workspace = temp_dir / "ddk_workspace" |
| kleaf_repo = temp_dir / "kleaf_repo" |
| try: |
| init_ddk.KleafProjectSetter( |
| build_id=None, |
| build_target=None, |
| ddk_workspace=ddk_workspace, |
| kleaf_repo=kleaf_repo, |
| local=False, |
| prebuilts_dir=None, |
| url_fmt=None, |
| superproject_tool="repo", |
| sync=False, |
| ).run() |
| except: # pylint: disable=bare-except |
| pass |
| finally: |
| self.assertTrue(ddk_workspace.exists()) |
| self.assertTrue(kleaf_repo.exists()) |
| |
| def test_tools_bazel_symlink(self): |
| """Tests a symlink to tools/bazel is correctly created.""" |
| with tempfile.TemporaryDirectory() as temp_dir: |
| temp_dir = pathlib.Path(temp_dir) |
| ddk_workspace = temp_dir / "ddk_workspace" |
| try: |
| init_ddk.KleafProjectSetter( |
| build_id=None, |
| build_target=None, |
| ddk_workspace=ddk_workspace, |
| kleaf_repo=temp_dir / "kleaf_repo", |
| local=False, |
| prebuilts_dir=None, |
| url_fmt=None, |
| superproject_tool="repo", |
| sync=False, |
| ).run() |
| except BaseException as e: # pylint: disable=bare-except |
| logging.error(e) |
| finally: |
| tools_bazel_symlink = ddk_workspace / init_ddk._TOOLS_BAZEL |
| self.assertTrue(tools_bazel_symlink.is_symlink()) |
| |
| def _run_test_module_bazel_for_prebuilts( |
| self, |
| ddk_workspace: pathlib.Path, |
| prebuilts_dir: pathlib.Path, |
| expected: str, |
| ): |
| """Helper method for checking path in a prebuilt extension.""" |
| ci_target_mapping = prebuilts_dir / "ci_target_mapping.json" |
| ci_target_mapping.parent.mkdir(parents=True) |
| ci_target_mapping.write_text("{}") |
| try: |
| init_ddk.KleafProjectSetter( |
| build_id=None, |
| build_target=None, |
| ddk_workspace=ddk_workspace, |
| kleaf_repo=None, |
| local=False, |
| prebuilts_dir=prebuilts_dir, |
| url_fmt=None, |
| superproject_tool="repo", |
| sync=False, |
| ).run() |
| except: # pylint: disable=bare-except |
| pass |
| finally: |
| module_bazel = ddk_workspace / init_ddk._MODULE_BAZEL_FILE |
| self.assertTrue(module_bazel.exists()) |
| content = module_bazel.read_text() |
| self.assertTrue(f'local_artifact_path = "{expected}",' in content) |
| |
| def test_module_bazel_for_prebuilts(self): |
| """Tests prebuilts setup is correct for relative and non-relative to workspace dirs.""" |
| with tempfile.TemporaryDirectory() as tmp: |
| ddk_workspace = pathlib.Path(tmp) / "ddk_workspace" |
| # Verify the right local_artifact_path is set for prebuilts |
| # in a relative to workspace directory. |
| prebuilts_dir_rel = ddk_workspace / "prebuilts_dir" |
| self._run_test_module_bazel_for_prebuilts( |
| ddk_workspace=ddk_workspace, |
| prebuilts_dir=prebuilts_dir_rel, |
| expected="prebuilts_dir", |
| ) |
| |
| # Verify the right local_artifact_path is set for prebuilts |
| # in a non-relative to workspace directory. |
| prebuilts_dir_abs = pathlib.Path(tmp) / "prebuilts_dir" |
| self._run_test_module_bazel_for_prebuilts( |
| ddk_workspace=ddk_workspace, |
| prebuilts_dir=prebuilts_dir_abs, |
| expected=str(prebuilts_dir_abs), |
| ) |
| |
| def test_repo_name(self): |
| """Tests that repo_name in ci_target_mapping.json is respected.""" |
| with tempfile.TemporaryDirectory() as tmp: |
| ddk_workspace = pathlib.Path(tmp) / "ddk_workspace" |
| # Verify the right local_artifact_path is set for prebuilts |
| # in a relative to workspace directory. |
| prebuilts_dir = ddk_workspace / "prebuilts_dir" |
| |
| ci_target_mapping = prebuilts_dir / "ci_target_mapping.json" |
| ci_target_mapping.parent.mkdir(parents=True) |
| ci_target_mapping.write_text(f"""{{ |
| "repo_name": "my_gki_prebuilts" |
| }}""") |
| try: |
| init_ddk.KleafProjectSetter( |
| build_id=None, |
| build_target=None, |
| ddk_workspace=ddk_workspace, |
| kleaf_repo=None, |
| local=False, |
| prebuilts_dir=prebuilts_dir, |
| url_fmt=None, |
| superproject_tool="repo", |
| sync=False, |
| ).run() |
| except: # pylint: disable=bare-except |
| pass |
| finally: |
| module_bazel = ddk_workspace / init_ddk._MODULE_BAZEL_FILE |
| self.assertTrue(module_bazel.exists()) |
| content = module_bazel.read_text() |
| self.assertIn(textwrap.dedent("""\ |
| kernel_prebuilt_ext.declare_kernel_prebuilts( |
| name = "my_gki_prebuilts", |
| local_artifact_path = "prebuilts_dir", |
| """), content) |
| |
| def test_download_works_for_local_file(self): |
| """Tests that local files can be downloaded.""" |
| with tempfile.TemporaryDirectory() as tmp_dir: |
| tmp_dir = pathlib.Path(tmp_dir) |
| remote_file = tmp_dir / "remote_file" |
| remote_file.write_text("Hello World!") |
| out_file = tmp_dir / "out_file" |
| url_fmt = f"file://{tmp_dir}/{{filename}}" |
| init_ddk.KleafProjectSetter( |
| build_id=None, |
| build_target=None, |
| ddk_workspace=None, |
| kleaf_repo=None, |
| local=False, |
| prebuilts_dir=None, |
| url_fmt=url_fmt, |
| superproject_tool="repo", |
| sync=False, |
| )._download( |
| remote_filename="remote_file", |
| out_file_name=out_file, |
| ) |
| self.assertTrue(out_file.exists()) |
| self.assertEqual(out_file.read_text(), "Hello World!") |
| |
| def test_non_mandatory_doesnt_fail(self): |
| """Tests that optional files don't produce errors.""" |
| with tempfile.TemporaryDirectory() as tmp: |
| ddk_workspace = pathlib.Path(tmp) / "ddk_workspace" |
| prebuilts_dir = ddk_workspace / "prebuilts_dir" |
| ci_target_mapping = ddk_workspace / "ci_target_mapping.json" |
| ci_target_mapping.parent.mkdir(parents=True, exist_ok=True) |
| ci_target_mapping.write_text( |
| json.dumps({"download_configs": { |
| "non-existent-file": { |
| "target_suffix": "non-existent-file", |
| "mandatory": False, |
| "remote_filename_fmt": "non-existent-file", |
| } |
| }}) |
| ) |
| with open(ci_target_mapping, "r", encoding="utf-8"): |
| url_fmt = f"file://{str(ci_target_mapping.parent)}/{{filename}}" |
| init_ddk.KleafProjectSetter( |
| build_id="12345", |
| build_target=None, |
| ddk_workspace=ddk_workspace, |
| kleaf_repo=None, |
| local=False, |
| prebuilts_dir=prebuilts_dir, |
| url_fmt=url_fmt, |
| superproject_tool="repo", |
| sync=False, |
| ).run() |
| |
| @parameterized.named_parameters( |
| # (Name, MODULE.bazel in @kleaf, ProjectSyncStates, expectation) |
| ("Empty", "", None, ""), |
| ( |
| "Dependencies", |
| textwrap.dedent("""\ |
| local_path_override( |
| module_name = "abseil-py", |
| path = "external/python/absl-py", |
| ) |
| local_path_override( |
| module_name = "apple_support", |
| path = "external/bazelbuild-apple_support", |
| ) |
| """), |
| None, |
| textwrap.dedent("""\ |
| local_path_override( |
| module_name = "abseil-py", |
| path = "kleaf_repo/external/python/absl-py", |
| ) |
| local_path_override( |
| module_name = "apple_support", |
| path = "kleaf_repo/external/bazelbuild-apple_support", |
| ) |
| """), |
| ), |
| ( |
| "ProjectSyncStates", |
| textwrap.dedent("""\ |
| local_path_override( |
| module_name = "some_module", |
| path = "external/some_git_project/some_module", |
| ) |
| """), |
| ProjectSyncStates(synced=True, states={ |
| ProjectAbsState( |
| original_path=pathlib.Path("external/some_git_project"), |
| fixed_abs_path=pathlib.Path( |
| "ddk_workspace/mapped/external/some_git_project"), |
| ) |
| }), |
| textwrap.dedent("""\ |
| local_path_override( |
| module_name = "some_module", |
| path = "mapped/external/some_git_project/some_module", |
| ) |
| """), |
| ), |
| ) |
| def test_local_path_overrides_extraction( |
| self, current_content: str, |
| project_sync_states: ProjectSyncStates | None, |
| wanted_content: None |
| ): |
| """Tests extraction of local path overrides works correctly.""" |
| |
| with tempfile.TemporaryDirectory() as tmp: |
| if project_sync_states: |
| project_sync_states.states = { |
| ProjectAbsState(state.original_path, |
| tmp / state.fixed_abs_path) |
| for state in project_sync_states.states |
| } |
| ddk_workspace = pathlib.Path(tmp) / "ddk_workspace" |
| kleaf_repo = ddk_workspace / "kleaf_repo" |
| kleaf_repo.mkdir(parents=True, exist_ok=True) |
| kleaf_repo_module_bazel = kleaf_repo / init_ddk._MODULE_BAZEL_FILE |
| kleaf_repo_module_bazel.write_text(current_content) |
| project_setter = init_ddk.KleafProjectSetter( |
| build_id=None, |
| build_target=None, |
| ddk_workspace=ddk_workspace, |
| kleaf_repo=kleaf_repo, |
| local=project_sync_states is None, |
| prebuilts_dir=None, |
| url_fmt=None, |
| superproject_tool="repo", |
| sync=project_sync_states is not None, |
| ) |
| project_setter._project_sync_states = project_sync_states |
| self.assertEqual(project_setter._get_local_path_overrides(), |
| wanted_content) |
| |
| def test_update_manifest(self): |
| """Tests that the repo manifest is updated correctly.""" |
| with tempfile.TemporaryDirectory() as tmp: |
| tmp = pathlib.Path(tmp) |
| ddk_workspace = tmp / "ddk_workspace" |
| |
| repo_manifest = ddk_workspace / ".repo/manifests/default.xml" |
| repo_manifest.parent.mkdir(parents=True, exist_ok=True) |
| repo_manifest.write_text(textwrap.dedent("""\ |
| <?xml version="1.0" encoding="UTF-8"?> |
| <manifest> |
| <useless garbage="true" /> |
| </manifest> |
| """)) |
| |
| remote_prebuilts_dir = tmp / "remote_prebuilts_dir" |
| remote_prebuilts_dir.mkdir(parents=True, exist_ok=True) |
| ci_target_mapping = remote_prebuilts_dir / "ci_target_mapping.json" |
| ci_target_mapping.write_text(json.dumps({"download_configs": { |
| "manifest.xml": { |
| "target_suffix": "init_ddk_files", |
| "mandatory": False, |
| "remote_filename_fmt": "manifest_{build_number}.xml", |
| }, |
| }})) |
| |
| build_id = "12345" |
| downloaded_manifest = (remote_prebuilts_dir / |
| f"manifest_{build_id}.xml") |
| source = (pathlib.Path(__file__).parent / |
| "test_data/sample_manifest.xml") |
| shutil.copy(source, downloaded_manifest) |
| |
| init_ddk.KleafProjectSetter( |
| build_id=build_id, |
| build_target=None, |
| ddk_workspace=ddk_workspace, |
| kleaf_repo=ddk_workspace / "external/kleaf", |
| local=False, |
| prebuilts_dir=ddk_workspace / "prebuilts_dir", |
| url_fmt=f"file://{str(remote_prebuilts_dir)}/{{filename}}", |
| superproject_tool="repo", |
| sync=False, |
| ).run() |
| |
| with xml.dom.minidom.parse( |
| str(ddk_workspace / ".repo/manifests/kleaf.xml")) as dom: |
| |
| root: xml.dom.minidom.Element = dom.documentElement |
| self.assertFalse(root.getElementsByTagName("superproject")) |
| self.assertFalse(root.getElementsByTagName("default")) |
| self.assertTrue(root.getElementsByTagName("remote")) |
| |
| projects = root.getElementsByTagName("project") |
| project_paths = [] |
| links = {} |
| for project in projects: |
| project_path = pathlib.Path(project.getAttribute("path") |
| or project.getAttribute("name")) |
| project_paths.append(project_path) |
| for link in project.getElementsByTagName("linkfile"): |
| src = project_path / link.getAttribute("src") |
| dest = pathlib.Path(link.getAttribute("dest")) |
| links[dest] = src |
| |
| # Check <project> paths are fixed |
| self.assertCountEqual( |
| project_paths, [ |
| pathlib.Path("external/kleaf/build/kernel"), |
| pathlib.Path("external/bazel-skylib"), |
| ]) |
| |
| # Check <linkfile> is fixed |
| # pylint: disable=line-too-long |
| self.assertEqual(links, { |
| pathlib.Path("tools/bazel"): pathlib.Path("external/kleaf/build/kernel/kleaf/bazel.sh"), |
| pathlib.Path("external/kleaf/MODULE.bazel"): pathlib.Path("external/kleaf/build/kernel/kleaf/bzlmod/bazel.MODULE.bazel") |
| }) |
| |
| with xml.dom.minidom.parse( |
| str(ddk_workspace / ".repo/manifests/default.xml")) as dom: |
| root: xml.dom.minidom.Element = dom.documentElement |
| includes = root.getElementsByTagName("include") |
| include_names = [ |
| include.getAttribute("name") for include in includes |
| ] |
| self.assertListEqual(include_names, ["kleaf.xml"]) |
| |
| |
| # This could be run as: tools/bazel test //build/kernel:init_ddk_test --test_output=all |
| if __name__ == "__main__": |
| logging.basicConfig( |
| level=logging.DEBUG, format="%(levelname)s: %(message)s" |
| ) |
| absltest.main() |