diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000000..8cda398654 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,70 @@ +name: Validate Tryton customizations + +on: + push: + branches: + - '8.0' + - 'integration/8.0' + - 'feature/**' + - 'translation/**' + pull_request: + branches: + - '8.0' + - 'integration/8.0' + workflow_dispatch: + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-24.04 + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + cache: pip + + - name: Install validation dependencies + run: python -m pip install --upgrade build polib + + - name: Validate Python syntax + run: python -m compileall -q trytond/trytond modules/translation_do + + - name: Validate PO files + run: python ci/validate_po.py + + - name: Build customized packages + run: | + python -m build trytond + python -m build modules/translation_do + + - name: Install Tryton server and translation module + run: | + python -m pip install './trytond[test]' + python -m pip install modules/translation_do/dist/*.whl + + - name: Run relevant Tryton tests + env: + TRYTOND_DATABASE_URI: sqlite:// + DB_NAME: ':memory:' + run: | + python -m unittest discover -s ci/tests -p 'test_*.py' + python -m unittest discover -s trytond/trytond/tests -p 'test_i18n.py' + python -m unittest trytond.modules.translation_do.tests.test_module + + - name: Upload Python distributions + uses: actions/upload-artifact@v4 + with: + name: tryton-custom-python-${{ gitea.sha }} + path: | + trytond/dist/* + modules/translation_do/dist/* + if-no-files-found: error + retention-days: 14 diff --git a/ci/tests/test_upstream_pr.py b/ci/tests/test_upstream_pr.py new file mode 100644 index 0000000000..847bac1863 --- /dev/null +++ b/ci/tests/test_upstream_pr.py @@ -0,0 +1,60 @@ +import importlib.util +import os +import pathlib +import unittest +from unittest.mock import ANY, patch + + +SCRIPT = pathlib.Path(__file__).parents[1] / 'upstream_pr.py' + + +class UpstreamPullRequestTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + environment = { + 'GITEA_URL': 'https://gitea.example.test', + 'CUSTOM_REPOSITORY': 'tryton-do/tryton', + 'GITEA_TOKEN': 'test-token', + 'INTEGRATION_BRANCH': 'integration/8.0', + 'UPDATE_BRANCH': 'automation/upstream-8.0', + } + with patch.dict(os.environ, environment, clear=False): + spec = importlib.util.spec_from_file_location('upstream_pr', SCRIPT) + cls.module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(cls.module) + + def test_reuses_existing_pull_request(self): + existing = [{ + 'head': {'ref': self.module.HEAD}, + 'base': {'ref': self.module.TARGET}, + 'html_url': 'https://gitea.example.test/pulls/1', + }] + with patch.object(self.module, 'open_pulls', return_value=existing), \ + patch.object(self.module, 'request') as request: + self.module.ensure_pull_request('a' * 40) + request.assert_not_called() + + def test_creates_pull_request_with_review_target(self): + created = {'html_url': 'https://gitea.example.test/pulls/2'} + with patch.object(self.module, 'open_pulls', return_value=[]), \ + patch.object(self.module, 'request', return_value=created) as request: + self.module.ensure_pull_request('b' * 40) + request.assert_called_once_with( + 'POST', '/repos/tryton-do/tryton/pulls', ANY) + payload = request.call_args.args[2] + self.assertEqual(payload['head'], 'automation/upstream-8.0') + self.assertEqual(payload['base'], 'integration/8.0') + + def test_reuses_existing_conflict_issue(self): + existing = [{ + 'title': 'Upstream Tryton 8.0 synchronization conflict', + 'html_url': 'https://gitea.example.test/issues/1', + }] + with patch.object(self.module, 'request', return_value=existing) as request: + self.module.ensure_conflict_issue('trytond/file.py') + request.assert_called_once() + + +if __name__ == '__main__': + unittest.main() diff --git a/ci/validate_po.py b/ci/validate_po.py new file mode 100755 index 0000000000..e0d3f294f2 --- /dev/null +++ b/ci/validate_po.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Validate every Gettext catalog changed or maintained in this repository.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import polib + + +def main() -> int: + failures: list[str] = [] + catalogs = sorted(Path('.').glob('**/*.po')) + if not catalogs: + failures.append('no PO catalogs were found') + + for path in catalogs: + try: + catalog = polib.pofile(path, check_for_duplicates=True) + except (OSError, UnicodeError, ValueError) as exc: + failures.append(f'{path}: {exc}') + continue + if not catalog.metadata.get('Content-Type', '').startswith('text/plain'): + failures.append(f'{path}: missing or invalid Content-Type metadata') + + if failures: + print('\n'.join(failures), file=sys.stderr) + return 1 + print(f'Validated {len(catalogs)} PO catalogs') + return 0 + + +if __name__ == '__main__': + raise SystemExit(main())