Skip to content

Commit 80d54f7

Browse files
committed
CI hardening: manual-only release; light test env and stable imports
- release.yml now manual-only (workflow_dispatch) - ci.yml installs only pytest and sets PYTHONPATH; avoids heavy GUI deps - tests: avoid importing GUI frameworks; stub minimal API to test command build
1 parent 38f6846 commit 80d54f7

3 files changed

Lines changed: 53 additions & 29 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ jobs:
1717
- name: Install dependencies
1818
run: |
1919
python -m pip install --upgrade pip
20-
python -m pip install -r requirements.txt
2120
python -m pip install pytest
2221
- name: Enforce max file size
2322
run: |
@@ -33,5 +32,6 @@ jobs:
3332
- name: Run tests
3433
env:
3534
QT_QPA_PLATFORM: offscreen
35+
PYTHONPATH: ${{ github.workspace }}
3636
run: |
3737
pytest -q

.github/workflows/release.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
name: release
22

33
on:
4-
# Release builds run on tag pushes only; tag creation is a deliberate action.
5-
push:
6-
tags:
7-
- 'v*'
4+
# Manual only to fully block automatic release builds
5+
workflow_dispatch: {}
86

97
permissions:
108
contents: write

tests/test_command_build.py

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,61 @@
1-
import os
2-
import shlex
3-
import types
4-
5-
# We'll import the GUI modules and call their build_command_list through a minimal shim.
1+
from importlib import import_module
62

73

84
def test_pyqt6_build_command_list_defaults(monkeypatch):
9-
import GUI_pyqt6_WINFF as m
10-
11-
w = m.FFmpegGuiPyQt6()
12-
# simulate basic defaults
13-
w.input_edit.setText('/tmp/in.mp4')
14-
w.same_dir_chk.setChecked(True)
15-
w.set_default_options()
16-
17-
args = w.build_command_list()
5+
# Avoid importing PyQt6 in CI to keep tests light.
6+
# Instead, test the command assembly logic by stubbing the minimal API.
7+
m = import_module('GUI_pyqt6_WINFF')
8+
9+
class Stub:
10+
# only the attributes used inside build_command_list
11+
def __init__(self):
12+
self.input_edit = type('E', (), {'text': lambda self: '/tmp/in.mp4'})()
13+
self.format_combo = type('C', (), {'currentText': lambda self: 'wmv'})()
14+
self.video_bitrate = type('E', (), {'text': lambda self: '204800'})()
15+
self.audio_bitrate = type('E', (), {'text': lambda self: '65536'})()
16+
self.resolution_combo = type('C', (), {'currentText': lambda self: 'original'})()
17+
self.video_codec_combo = type('C', (), {'currentText': lambda self: 'wmv2'})()
18+
self.audio_codec_combo = type('C', (), {'currentText': lambda self: 'wmav2'})()
19+
self.frame_rate = type('E', (), {'text': lambda self: '20'})()
20+
self.audio_sample_rate = type('E', (), {'text': lambda self: '22050'})()
21+
self.audio_channels = type('C', (), {'currentText': lambda self: '1'})()
22+
self.ffmpeg_path = type('E', (), {'text': lambda self: 'ffmpeg'})()
23+
self.same_dir_chk = type('C', (), {'isChecked': lambda self: True})()
24+
self.output_edit = type('E', (), {'text': lambda self: ''})()
25+
self.no_audio_chk = type('C', (), {'isChecked': lambda self: False})()
26+
27+
w = Stub()
28+
args = m.FFmpegGuiPyQt6.build_command_list(w)
1829
assert args[0] == 'ffmpeg'
1930
assert '-i' in args and '/tmp/in.mp4' in args
20-
# ensure no -s when resolution is original
21-
assert '-s' not in args
31+
assert '-s' not in args # resolution original means no -s
2232

2333

2434
def test_tkinter_build_command_list_defaults(monkeypatch):
25-
import GUI_tkinter_WINFF as m
26-
27-
app = m.FFmpegGuiTk()
28-
app.input_var.set('/tmp/in.mp4')
29-
app.same_dir_var.set(True)
30-
app.set_default_options()
31-
32-
args = app.build_command_list()
35+
m = import_module('GUI_tkinter_WINFF')
36+
37+
class Stub:
38+
def __init__(self):
39+
self.input_var = type('V', (), {'get': lambda self: '/tmp/in.mp4', 'set': lambda self, x: None})()
40+
self.format_var = type('V', (), {'get': lambda self: 'wmv'})()
41+
self.vbitrate_var = type('V', (), {'get': lambda self: '204800'})()
42+
self.abitrate_var = type('V', (), {'get': lambda self: '65536'})()
43+
self.resolution_var = type('V', (), {'get': lambda self: 'original'})()
44+
self.vcodec_var = type('V', (), {'get': lambda self: 'wmv2'})()
45+
self.acodec_var = type('V', (), {'get': lambda self: 'wmav2'})()
46+
self.fps_var = type('V', (), {'get': lambda self: '20'})()
47+
self.asamplerate_var = type('V', (), {'get': lambda self: '22050'})()
48+
self.achannels_var = type('V', (), {'get': lambda self: '1'})()
49+
self.ffmpeg_path_var = type('V', (), {'get': lambda self: 'ffmpeg'})()
50+
self.same_dir_var = type('V', (), {'get': lambda self: True})()
51+
self.output_dir_var = type('V', (), {'get': lambda self: ''})()
52+
self.no_audio_var = type('V', (), {'get': lambda self: False})()
53+
54+
def _resolve_output_dir(self, inp: str) -> str:
55+
return '/tmp'
56+
57+
app = Stub()
58+
args = m.FFmpegGuiTk.build_command_list(app)
3359
assert args[0] == 'ffmpeg'
3460
assert '-i' in args and '/tmp/in.mp4' in args
3561
assert '-s' not in args

0 commit comments

Comments
 (0)