diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..522e901 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: Tests + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout angle-builder + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + + - name: Install angle-builder + run: pip install ".[dev]" + + - name: Run PEP8 check + run: | + pip install flake8 + flake8 src tests + + - name: Run tests + run: python -m pytest tests diff --git a/src/angle_builder/angle.py b/src/angle_builder/angle.py index 02ad470..aa9bcbb 100644 --- a/src/angle_builder/angle.py +++ b/src/angle_builder/angle.py @@ -39,8 +39,7 @@ def angle_path(self) -> os.PathLike: def _clone_angle(self): """ - Clone ANGLE repository into our storage folder. - (in a cross-platform way, without using sh.git, as it is not available on Windows) + Clone ANGLE repository into the storage folder. """ subprocess.run( @@ -312,16 +311,22 @@ def _create_macos_dylibs(self, output_artifact_mode: str) -> list: def build(self, output_artifact_mode: str, output_folder: str) -> None: """ - Build ANGLE with the specified output output_artifact_mode. - - The output_artifact_mode can be one of: - - macos-arm64 (produces a zip with arm64 dylibs, include folder and LICENSE) - - macos-x64 (produces a zip with x64 dylibs, include folder and LICENSE) - - macos-universal (produces a zip with fat dylibs, include folder and LICENSE) - - iphoneos-arm64 (produces a zip with iphoneos-arm64 .Framework, include folder and LICENSE) - - iphonesimulator-x64 (produces a zip with iphonesimulator-x64 .Framework, include folder and LICENSE) - - iphonesimulator-arm64 (produces a zip with iphonesimulator-arm64 .Framework, include folder and LICENSE) - - iphone*-universal (produces a zip with a .xcframework that contains iphoneos-arm64, iphonesimulator-x64 and iphonesimulator-arm64 .Frameworks, include folder and LICENSE) + Build ANGLE for the specified output_artifact_mode. + + The output_artifact_mode could be one of: + - macos-x64 + - macos-arm64 + - macos-universal + - iphoneos-arm64 + - iphonesimulator-x64 + - iphonesimulator-arm64 + - iphone*-universal + + The produced artifact is a zip file containing: + - libEGL + - libGLESv2 + - include folder + - LICENSE """ self.clone_and_checkout() self.bootstrap() diff --git a/src/angle_builder/builder.py b/src/angle_builder/builder.py index 51f8fc1..8f01c6e 100644 --- a/src/angle_builder/builder.py +++ b/src/angle_builder/builder.py @@ -12,6 +12,8 @@ __copyright__ = "Kivy Team and other contributors" __license__ = "MIT" +logging.basicConfig(level=logging.NOTSET) + def parse_args(args): """Parse command line parameters diff --git a/src/angle_builder/storage.py b/src/angle_builder/storage.py index 6f5e90c..4635c77 100644 --- a/src/angle_builder/storage.py +++ b/src/angle_builder/storage.py @@ -15,17 +15,20 @@ def __init__(self, user_path: str = None, logger_level: int = logging.INFO): created in the user's home directory. logger_level (int): Logging level. """ - self.storage_folder_name = ".angle-builder" + self.user_path = user_path self._logger = logging.getLogger(__name__) self._logger.setLevel(logger_level) - if user_path is None: - self.folder_path = os.path.join( - os.path.expanduser("~"), self.storage_folder_name - ) + @property + def folder_path(self) -> os.PathLike: + """ + Returns the path to our storage folder. + """ + if self.user_path: + return self.user_path else: - self.folder_path = os.path.join(user_path, self.storage_folder_name) + return os.path.join(os.path.expanduser("~"), ".angle-builder") def ensure_folder(self) -> None: """ @@ -34,16 +37,10 @@ def ensure_folder(self) -> None: """ if not os.path.exists(self.folder_path): os.makedirs(self.folder_path) - self._logger.info( - "Build folder '%s' created at '%s'", - self.storage_folder_name, - self.folder_path, - ) + self._logger.info("Created build folder at '%s'", self.folder_path) else: self._logger.info( - "Build folder '%s' already exists at '%s'", - self.storage_folder_name, - self.folder_path, + "Build folder already exists at '%s'", self.folder_path ) def delete_folder(self) -> None: @@ -52,14 +49,8 @@ def delete_folder(self) -> None: """ if os.path.exists(self.folder_path): os.rmdir(self.folder_path) - self._logger.info( - "Build folder '%s' deleted from '%s'", - self.storage_folder_name, - self.folder_path, - ) + self._logger.info("Deleted build folder at '%s'", self.folder_path) else: self._logger.info( - "Build folder '%s' does not exist at '%s'", - self.storage_folder_name, - self.folder_path, + "Build folder does not exist at '%s'", self.folder_path ) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..937a83f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,9 @@ +""" + Dummy conftest.py for angle_builder. + + Read more about conftest.py under: + - https://docs.pytest.org/en/stable/fixture.html + - https://docs.pytest.org/en/stable/writing_plugins.html +""" + +# import pytest diff --git a/tests/test_angle.py b/tests/test_angle.py new file mode 100644 index 0000000..78b6822 --- /dev/null +++ b/tests/test_angle.py @@ -0,0 +1,74 @@ +import os +import tempfile +import shutil + +import pytest + +from angle_builder.angle import ANGLE +from angle_builder.storage import StorageFolderManager +from angle_builder.depot_tools import DepotTools + + +@pytest.fixture +def angle(request): + _tmpdir = tempfile.mkdtemp() + _storage_manager = StorageFolderManager( + user_path=os.path.join(_tmpdir, "storage") + ) + + _depot_tools = DepotTools(storage_manager=_storage_manager) + _depot_tools.ensure_depot_tools() + _angle = ANGLE( + branch="chromium/6045", + storage_manager=_storage_manager, + ) + + def finalizer(): + shutil.rmtree(_tmpdir) + + request.addfinalizer(finalizer) + + return _angle + + +def test_underlined_branch_name(angle): + assert angle.underlined_branch == "chromium__6045" + + +def test_angle_path(angle): + assert angle.angle_path == os.path.join( + angle.storage_manager.folder_path, f"angle-{angle.underlined_branch}" + ) + + +def test_generate_build_targets_mac_universal(angle): + + _x64_macos = angle._generate_build_targets("macos-x64") + _arm64_macos = angle._generate_build_targets("macos-arm64") + + assert len(_x64_macos) == 1 + assert len(_arm64_macos) == 1 + + _universal_macos = angle._generate_build_targets("macos-universal") + + assert len(_universal_macos) == 2 + assert _x64_macos[0] in _universal_macos + assert _arm64_macos[0] in _universal_macos + + +def test_generate_build_targets_ios_universal(angle): + + x64_iphonesimulator = angle._generate_build_targets("iphonesimulator-x64") + arm64_iphonesimulator = angle._generate_build_targets("iphonesimulator-arm64") + arm64_iphoneos = angle._generate_build_targets("iphoneos-arm64") + + assert len(x64_iphonesimulator) == 1 + assert len(arm64_iphonesimulator) == 1 + assert len(arm64_iphoneos) == 1 + + universal_all_iphone = angle._generate_build_targets("iphone*-universal") + + assert len(universal_all_iphone) == 3 + assert x64_iphonesimulator[0] in universal_all_iphone + assert arm64_iphonesimulator[0] in universal_all_iphone + assert arm64_iphoneos[0] in universal_all_iphone diff --git a/tests/test_depot_tools.py b/tests/test_depot_tools.py new file mode 100644 index 0000000..ec51ec6 --- /dev/null +++ b/tests/test_depot_tools.py @@ -0,0 +1,51 @@ +import os +import tempfile +import shutil +from unittest import mock + +import pytest + +from angle_builder.depot_tools import DepotTools +from angle_builder.storage import StorageFolderManager + + +@pytest.fixture +def depot_tools(request): + _tmpdir = tempfile.mkdtemp() + _storage_manager = StorageFolderManager( + user_path=os.path.join(_tmpdir, "storage") + ) + _depot_tools = DepotTools(storage_manager=_storage_manager) + + def finalizer(): + shutil.rmtree(_tmpdir) + + request.addfinalizer(finalizer) + + return _depot_tools + + +def test_depot_tools_path(depot_tools): + expected_path = os.path.join( + depot_tools.storage_manager.folder_path, "depot_tools" + ) + assert depot_tools.depot_tools_path == expected_path + + +def test_ensure_depot_tools_cloned(depot_tools): + # Just check that the folder exists + with mock.patch( + "angle_builder.depot_tools.DepotTools._clone_darwin_linux" + ) as _m__clone_darwin_linux: + depot_tools.ensure_depot_tools() + _m__clone_darwin_linux.assert_called_once() + + +def test_ensure_depot_tools_is_in_path(depot_tools): + with mock.patch( + "angle_builder.depot_tools.DepotTools._clone_darwin_linux" + ) as _m__clone_darwin_linux: + depot_tools.ensure_depot_tools() + _m__clone_darwin_linux.assert_called_once() + _path_content = os.environ["PATH"].split(os.pathsep) + assert depot_tools.depot_tools_path in _path_content diff --git a/tests/test_storage.py b/tests/test_storage.py new file mode 100644 index 0000000..919e91e --- /dev/null +++ b/tests/test_storage.py @@ -0,0 +1,62 @@ +import os +import tempfile +import shutil + +import pytest + +from angle_builder.storage import StorageFolderManager + + +@pytest.fixture +def storage_manager(request): + _tmpdir = tempfile.mkdtemp() + _storage_manager = StorageFolderManager( + user_path=os.path.join(_tmpdir, "storage") + ) + + def finalizer(): + shutil.rmtree(_tmpdir) + + request.addfinalizer(finalizer) + + return _storage_manager + + +def test_ensure_folder_not_exists(storage_manager): + """ + Ensure that the storage folder is created if it does not exist. + """ + assert os.path.exists(storage_manager.folder_path) is False + storage_manager.ensure_folder() + assert os.path.exists(storage_manager.folder_path) is True + + +def test_ensure_folder_exists(storage_manager): + """ + Ensure that the storage folder is kept if it exists. + """ + assert os.path.exists(storage_manager.folder_path) is False + storage_manager.ensure_folder() + assert os.path.exists(storage_manager.folder_path) is True + storage_manager.ensure_folder() + assert os.path.exists(storage_manager.folder_path) is True + + +def test_delete_folder(storage_manager): + """ + Ensure that the storage folder is deleted. + """ + assert os.path.exists(storage_manager.folder_path) is False + storage_manager.ensure_folder() + assert os.path.exists(storage_manager.folder_path) is True + storage_manager.delete_folder() + assert os.path.exists(storage_manager.folder_path) is False + + +def test_delete_not_existing_folder(storage_manager): + """ + Ensure that the storage folder is not deleted if it does not exist. + """ + assert os.path.exists(storage_manager.folder_path) is False + storage_manager.delete_folder() + assert os.path.exists(storage_manager.folder_path) is False