From d50713e710f48d51ddaa1a5267b7bd6d1b9238f2 Mon Sep 17 00:00:00 2001 From: why-not-try-calmer Date: Wed, 22 Feb 2023 16:44:03 +0100 Subject: [PATCH] Added tests from qgis-plug-ci --- pytransifex/api.py | 7 +- tests/_translation.py | 236 ++++++++++++++++++ .../data/.qgis-plugin-ci-test-changelog.yaml | 4 + tests/test_translation.py | 54 ++++ 4 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 tests/_translation.py create mode 100644 tests/data/.qgis-plugin-ci-test-changelog.yaml create mode 100644 tests/test_translation.py diff --git a/pytransifex/api.py b/pytransifex/api.py index 73bb833..1970f67 100644 --- a/pytransifex/api.py +++ b/pytransifex/api.py @@ -260,12 +260,9 @@ def create_language( def project_exists(self, project_slug: str) -> bool: """Check if the project exists in the remote Transifex repository""" try: - if not self.projects: - return False - elif self.get_project(project_slug=project_slug): + if self.get_project(project_slug=project_slug): return True - else: - return False + return False except DoesNotExist: return False diff --git a/tests/_translation.py b/tests/_translation.py new file mode 100644 index 0000000..bcce577 --- /dev/null +++ b/tests/_translation.py @@ -0,0 +1,236 @@ +import glob +import logging +import os +import subprocess +import sys +from dataclasses import dataclass +from datetime import date +from pathlib import Path + +from pytransifex.api import Transifex + +logger = logging.getLogger(__name__) + + +@dataclass +class Parameters: + changelog_include: bool + changelog_path: str + changelog_number_of_entries: int + create_date: date + github_organization_slug: str + lrelease_path: str + plugin_name: str + plugin_path: str + project_slug: str + pylupdate5_path: str + repository_url: str + transifex_coordinator: str + transifex_organization: str + transifex_project: str + transifex_resource: str + translation_source_language: str + translation_languages: str + + +def touch_file(path, update_time: bool = False, create_dir: bool = True): + basedir = os.path.dirname(path) + if create_dir and not os.path.exists(basedir): + os.makedirs(basedir) + with open(path, "a"): + if update_time: + os.utime(path, None) + else: + pass + + +class Translation: + def __init__( + self, parameters: Parameters, transifex_token: str, create_project: bool = True + ): + client = Transifex( + api_token=transifex_token, + organization_name=parameters.transifex_organization, + i18n_type="QT", + ) + assert client + + self.tx_client = client + self.parameters = parameters + + assert self.tx_client.ping() + plugin_path = self.parameters.plugin_path + tx = self.parameters.transifex_resource + lang = self.parameters.translation_source_language + self.ts_file = f"{plugin_path}/i18n/{tx}_{lang}.ts" + + if self.tx_client.project_exists(parameters.transifex_project): + logger.debug( + f"Project {self.parameters.transifex_organization}/" + f"{self.parameters.transifex_project} exists on Transifex" + ) + + elif create_project: + logger.debug( + "Project does not exists on Transifex, creating one as: " + f"{self.parameters.transifex_organization}/" + f"{self.parameters.transifex_project}" + ) + self.tx_client.create_project( + project_slug=self.parameters.transifex_project, + private=False, + repository_url=self.parameters.repository_url, + source_language_code=parameters.translation_source_language, + ) + assert self.tx_client.project_exists(self.parameters.transifex_project) + self.update_strings() + logger.debug( + f"Creating resource in {self.parameters.transifex_organization}/" + f"{self.parameters.transifex_project}/" + f"{self.parameters.transifex_resource} with {self.ts_file}" + ) + self.tx_client.create_resource( + project_slug=self.parameters.transifex_project, + path_to_file=self.ts_file, + resource_slug=self.parameters.transifex_resource, + ) + logger.info( + f""" + Transifex project {self.parameters.transifex_organization}/ + {self.parameters.transifex_project} and resource ({self.parameters.transifex_resource}) have been created. + """ + ) + else: + logger.error( + "Project does not exists on Transifex: " + f"{self.parameters.transifex_organization}/" + f"{self.parameters.transifex_project}" + ) + + def update_strings(self): + """ + Update TS files from plugin source strings + """ + source_py_files = [] + source_ui_files = [] + relative_path = f"./{self.parameters.plugin_path}" + for ext in ("py", "ui"): + for file in glob.glob( + f"{self.parameters.plugin_path}/**/*.{ext}", + recursive=True, + ): + file_path = str(Path(file).relative_to(relative_path)) + if ext == "py": + source_py_files.append(file_path) + else: + source_ui_files.append(file_path) + + touch_file(self.ts_file) + + project_file = Path(self.parameters.plugin_path).joinpath( + self.parameters.plugin_name + ".pro" + ) + + with open(project_file, "w") as f: + source_py_files = " ".join(source_py_files) + source_ui_files = " ".join(source_ui_files) + assert f.write("CODECFORTR = UTF-8\n") + assert f.write(f"SOURCES = {source_py_files}\n") + assert f.write(f"FORMS = {source_ui_files}\n") + assert f.write( + f"TRANSLATIONS = {Path(self.ts_file).relative_to(relative_path)}\n" + ) + f.flush() + f.close() + + cmd = [self.parameters.pylupdate5_path, "-noobsolete", str(project_file)] + + output = subprocess.run(cmd, capture_output=True, text=True) + + project_file.unlink() + + if output.returncode != 0: + logger.error(f"Translation failed: {output.stderr}") + sys.exit(1) + else: + logger.info(f"Successfully run pylupdate5: {output.stdout}") + + def compile_strings(self): + """ + Compile TS file into QM files + """ + cmd = [self.parameters.lrelease_path] + for file in glob.glob(f"{self.parameters.plugin_path}/i18n/*.ts"): + cmd.append(file) + output = subprocess.run(cmd, capture_output=True, text=True) + if output.returncode != 0: + logger.error(f"Translation failed: {output.stderr}") + sys.exit(1) + else: + logger.info(f"Successfully run lrelease: {output.stdout}") + + def pull(self): + """ + Pull TS files from Transifex + """ + resource = self.__get_resource() + existing_langs = self.tx_client.list_languages( + project_slug=self.parameters.transifex_project + ) + existing_langs.remove(self.parameters.translation_source_language) + logger.info( + f"{len(existing_langs)} languages found for resource {resource.get('slug')}:" + f" ({existing_langs})" + ) + for lang in self.parameters.translation_languages: + if lang not in existing_langs: + logger.debug(f"Creating missing language: {lang}") + self.tx_client.create_language( + project_slug=self.parameters.transifex_project, + language_code=lang, + coordinators=[self.parameters.transifex_coordinator], + ) + existing_langs.append(lang) + for lang in existing_langs: + ts_file = f"{self.parameters.plugin_path}/i18n/{self.parameters.transifex_resource}_{lang}.ts" + logger.debug(f"Downloading translation file: {ts_file}") + self.tx_client.get_translation( + project_slug=self.parameters.transifex_project, + resource_slug=resource["slug"], + language_code=lang, + path_to_output_file=ts_file, + ) + + def push(self): + resource = self.__get_resource() + logger.debug( + f"Pushing resource: {self.parameters.transifex_resource} " + f"with file {self.ts_file}" + ) + result = self.tx_client.update_source_translation( + project_slug=self.parameters.transifex_project, + resource_slug=resource["slug"], + path_to_file=self.ts_file, + ) + logger.info(f"Translation resource updated: {result}") + + def __get_resource(self) -> dict: + resources = self.tx_client.list_resources(self.parameters.transifex_project) + if len(resources) == 0: + logger.error( + f"Project '{self.parameters.transifex_project}' has no resource on Transifex" + ) + sys.exit(1) + if len(resources) > 1: + for resource in resources: + if resource["name"] == self.parameters.transifex_resource: + return resource + logger.error( + f"Project '{self.parameters.transifex_project}' has several " + "resources on Transifex and none is named as the project slug. " + "Specify one in the parameters with transifex_resource." + "These resources have been found: " + f"{', '.join([r['name'] for r in resources])}" + ) + sys.exit(1) + return resources[0] diff --git a/tests/data/.qgis-plugin-ci-test-changelog.yaml b/tests/data/.qgis-plugin-ci-test-changelog.yaml new file mode 100644 index 0000000..d00a409 --- /dev/null +++ b/tests/data/.qgis-plugin-ci-test-changelog.yaml @@ -0,0 +1,4 @@ +plugin_path: qgis_plugin_CI_testing +github_organization_slug: opengisch +project_slug: qgis-plugin-ci-changelog +create_date: 1985-07-21 diff --git a/tests/test_translation.py b/tests/test_translation.py new file mode 100644 index 0000000..da9c955 --- /dev/null +++ b/tests/test_translation.py @@ -0,0 +1,54 @@ +import logging +import os +import unittest +from pathlib import Path +import yaml + +from pytransifex.exceptions import PyTransifexException +from tests._translation import Parameters, Translation + +logger = logging.getLogger(__name__) + + +class TestTranslation(unittest.TestCase): + @classmethod + def setUpClass(cls): + """Initialize the test case""" + config_yaml = Path.cwd().joinpath("tests", "data", ".qgis-plugin-ci-test-changelog.yaml") + print(config_yaml) + with open(config_yaml) as f: + arg_dict = yaml.safe_load(f) + transifex_token = os.getenv("transifex_token") + assert transifex_token + cls.transifex_token = transifex_token + + cls.parameters = Parameters(**arg_dict) + cls.t = Translation(cls.parameters, transifex_token=transifex_token) + + def tearDown(self): + try: + self.t.tx_client.delete_project(self.parameters.project_slug) + except PyTransifexException as error: + logger.debug(error) + """ + try: + self.t.tx_client.delete_team(f"{self.parameters.project_slug}-team") + except PyTransifexException as error: + logger.debug(error) + """ + + def test_creation(self): + self.tearDown() + self.t = Translation(self.parameters, transifex_token=self.transifex_token) # type: ignore + + def test_push(self): + self.t.update_strings() + self.t.push() + + def test_pull(self): + self.t.pull() + self.t.compile_strings() + + +if __name__ == "__main__": + unittest.main()