diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afb2e50..b46966b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,14 +2,22 @@ name: CI # Checks only: nothing is committed or published. Runs in an Arch Linux # container with the official repositories, like the package build. +# A branch with a pull request is checked once, by the pull_request event +# (push alone would run the same commit twice). main is checked on push. on: pull_request: push: - branches: [ "**" ] + branches: [ main ] + workflow_dispatch: permissions: contents: read +# A newer commit on the same pull request or branch replaces the run in progress. +concurrency: + group: ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: python: runs-on: ubuntu-latest diff --git a/.github/workflows/translate-and-build-package.yml b/.github/workflows/translate-and-build-package.yml index de407b7..60a95c5 100644 --- a/.github/workflows/translate-and-build-package.yml +++ b/.github/workflows/translate-and-build-package.yml @@ -16,9 +16,19 @@ on: permissions: contents: write +# One run per branch at a time: each one commits and pushes translations. +concurrency: + group: translate-${{ github.ref }} + cancel-in-progress: false + jobs: build: + # Only in the biglinux organization: forks have neither the translation + # keys nor the builder token, and the translation step would hang until + # GitHub's 6-hour limit. + if: github.repository_owner == 'biglinux' runs-on: ubuntu-latest + timeout-minutes: 90 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/tests/test_history_recovery.py b/tests/test_history_recovery.py index 18fed18..8261352 100644 --- a/tests/test_history_recovery.py +++ b/tests/test_history_recovery.py @@ -153,17 +153,14 @@ def test_save_never_overwrites_an_existing_entry(hdir, tmp_path): def test_unwritable_folder_is_reported_not_raised(tmp_path, monkeypatch): - # The Music folder is read-only: the history folder cannot be created. - locked = tmp_path / "locked" - locked.mkdir() - locked.chmod(0o500) - monkeypatch.setattr(hs, "_MUSIC_DIR", locked) - try: - assert hs.save_reading(text="x", backend="rhvoice", voice_id="v") is None - assert hs.new_recording("rhvoice", time.time()) is None - assert hs.repair_history()["error"] == "" # nothing to repair, no crash - finally: - locked.chmod(0o700) + # The history folder cannot be created. (Its parent is a file, not a + # read-only folder: CI runs as root, and root ignores permissions.) + blocker = tmp_path / "blocker" + blocker.write_text("") + monkeypatch.setattr(hs, "_MUSIC_DIR", blocker / "Music") + assert hs.save_reading(text="x", backend="rhvoice", voice_id="v") is None + assert hs.new_recording("rhvoice", time.time()) is None + assert hs.repair_history()["error"] == "" # nothing to repair, no crash def test_durations_of_older_entries_are_filled_in(hdir, tmp_path): diff --git a/tests/test_tts_streaming.py b/tests/test_tts_streaming.py index 1e49438..08df17b 100644 --- a/tests/test_tts_streaming.py +++ b/tests/test_tts_streaming.py @@ -327,18 +327,17 @@ def test_readings_in_sequence_each_have_an_entry(fakes): def test_history_folder_without_permission_never_breaks_reading(fakes, monkeypatch): - locked = fakes / "locked" # read-only Music folder: no history folder possible - locked.mkdir() - locked.chmod(0o500) - monkeypatch.setattr(hs, "_MUSIC_DIR", locked) - try: - svc = ts.TTSService(settings=_settings()) - assert svc.speak("Ainda assim leio.", backend="rhvoice", voice_id="x", volume=50) - _run_watch(svc) - assert svc.state is TTSState.IDLE # the reading itself is fine - assert played(fakes) - finally: - locked.chmod(0o700) + # No history folder possible: its parent is a file (root-proof, unlike a + # read-only folder — CI runs as root). + blocker = fakes / "blocker" + blocker.write_text("") + monkeypatch.setattr(hs, "_MUSIC_DIR", blocker / "Music") + svc = ts.TTSService(settings=_settings()) + assert svc.speak("Ainda assim leio.", backend="rhvoice", voice_id="x", volume=50) + _run_watch(svc) + assert svc.state is TTSState.IDLE # the reading itself is fine + assert played(fakes) + assert not hs.get_history_dir().exists() @pytest.mark.parametrize(("backend", "voice"), [("rhvoice", "x"), ("kokoro", "kokoro:pf_dora")])