From 71a22c6a121d1294a05ef35020c525fa70cae2fd Mon Sep 17 00:00:00 2001 From: Brianna Rainey Date: Thu, 29 Jan 2026 14:50:29 -0500 Subject: v2.2.2 - fix flaky tests, fix UnboundLocalError in MainWindow.createNewProject (#94) * search more than one filename for compName * remove extra QVBoxLayout * insert default components using name instead of index * bump version 2.2.1 to 2.2.2 * tests do not use `.config` for settings Core.storeSettings() is no longer called as a side effect any time avp.core is imported. Thus the tests use a new `initCore` method and the normal user path now relies on entering via `cli.py`. This means certain toolkit functions (e.g., ones using `FFMPEG_BIN`) no longer work if imported from a different python script, unless they call Core.storeSettings() themselves to initialize the settings.ini file * fix UnboundLocalError in createNewProject--- tests/__init__.py | 40 ++++++++++++++++++++++++++++++++---- tests/data/inputfiles/test.jpg | Bin 0 -> 48766 bytes tests/data/inputfiles/test.ogg | Bin 0 -> 30043 bytes tests/data/inputfiles/test.png | Bin 0 -> 220 bytes tests/data/projects/testproject.avp | 17 +++++++++++++++ tests/data/test.jpg | Bin 48766 -> 0 bytes tests/data/test.ogg | Bin 30043 -> 0 bytes tests/data/test.png | Bin 220 -> 0 bytes tests/test_commandline_export.py | 10 +++------ tests/test_commandline_parser.py | 26 ++++++++--------------- tests/test_comp_color.py | 5 ++--- tests/test_comp_image.py | 11 +++++----- tests/test_comp_life.py | 5 ++--- tests/test_comp_original.py | 5 ++--- tests/test_comp_spectrum.py | 5 ++--- tests/test_comp_text.py | 5 ++--- tests/test_comp_waveform.py | 5 ++--- tests/test_mainwindow_projects.py | 40 ++++++++++++++++++++++++++++++++++++ tests/test_mainwindow_undostack.py | 15 +++----------- tests/test_toolkit_common.py | 10 ++++++--- tests/test_toolkit_ffmpeg.py | 4 +++- 21 files changed, 135 insertions(+), 68 deletions(-) create mode 100644 tests/data/inputfiles/test.jpg create mode 100644 tests/data/inputfiles/test.ogg create mode 100644 tests/data/inputfiles/test.png create mode 100644 tests/data/projects/testproject.avp delete mode 100644 tests/data/test.jpg delete mode 100644 tests/data/test.ogg delete mode 100644 tests/data/test.png create mode 100644 tests/test_mainwindow_projects.py (limited to 'tests') diff --git a/tests/__init__.py b/tests/__init__.py index b615681..df08c7c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,8 +1,9 @@ import os import numpy -# core always has to be imported first -import avp.core +from avp.core import Core +from avp.command import Command +from avp.gui.mainwindow import MainWindow from avp.toolkit.ffmpeg import readAudioFile from pytest import fixture @@ -10,16 +11,47 @@ from pytest import fixture @fixture def audioData(): """Fixture that gives a tuple of (completeAudioArray, duration)""" - soundFile = getTestDataPath("test.ogg") + # Core.storeSettings() needed to store ffmpeg bin location + initCore() + soundFile = getTestDataPath("inputfiles/test.ogg") yield readAudioFile(soundFile, MockVideoWorker()) -def getTestDataPath(filename): +@fixture +def command(qtbot): + initCore() + command = Command() + command.quit = lambda _: None + yield command + + +@fixture +def window(qtbot): + initCore() + window = MainWindow(None, None) + window.clear() + qtbot.addWidget(window) + window.settings.setValue("outputWidth", 1920) + window.settings.setValue("outputHeight", 1080) + yield window + + +def getTestDataPath(filename=""): """Get path to a file in the ./data directory""" tests_dir = os.path.dirname(os.path.abspath(__file__)) return os.path.join(tests_dir, "data", filename) +def initCore(): + testDataDir = getTestDataPath() + unwanted = ["autosave.avp", "settings.ini"] + for file in unwanted: + filename = os.path.join(testDataDir, "autosave.avp") + if os.path.exists(filename): + os.remove(filename) + Core.storeSettings(testDataDir) + + class MockSignal: """Pretends to be a pyqtSignal""" diff --git a/tests/data/inputfiles/test.jpg b/tests/data/inputfiles/test.jpg new file mode 100644 index 0000000..86266d9 Binary files /dev/null and b/tests/data/inputfiles/test.jpg differ diff --git a/tests/data/inputfiles/test.ogg b/tests/data/inputfiles/test.ogg new file mode 100644 index 0000000..46af76c Binary files /dev/null and b/tests/data/inputfiles/test.ogg differ diff --git a/tests/data/inputfiles/test.png b/tests/data/inputfiles/test.png new file mode 100644 index 0000000..f1ffd4a Binary files /dev/null and b/tests/data/inputfiles/test.png differ diff --git a/tests/data/projects/testproject.avp b/tests/data/projects/testproject.avp new file mode 100644 index 0000000..fd6b6eb --- /dev/null +++ b/tests/data/projects/testproject.avp @@ -0,0 +1,17 @@ +[Components] +Classic Visualizer +1 +OrderedDict({'bars': 63, 'layout': 0, 'preset': None, 'scale': 20, 'smooth': 0, 'visColor': (255, 255, 255), 'y': 0.0}) +Color +1 +OrderedDict({'LG_end': 0.0, 'LG_start': 0.0, 'RG_centre': 0.0015625, 'RG_end': 0.0, 'RG_start': 0.0, 'color1': (0, 0, 0), 'color2': (133, 133, 133), 'fillType': 0, 'height': 1.0, 'preset': None, 'spread': 0, 'stretch': False, 'trans': False, 'width': 1.0, 'x': 0.0, 'y': 0.0}) + +[Settings] +componentDir=tests/data/inputfiles +inputDir=tests/data/inputfiles +presetDir=tests/data/presets +projectDir=tests/data/projects + +[WindowFields] +lineEdit_audioFile=tests/data/test.ogg +lineEdit_outputFile= diff --git a/tests/data/test.jpg b/tests/data/test.jpg deleted file mode 100644 index 86266d9..0000000 Binary files a/tests/data/test.jpg and /dev/null differ diff --git a/tests/data/test.ogg b/tests/data/test.ogg deleted file mode 100644 index 46af76c..0000000 Binary files a/tests/data/test.ogg and /dev/null differ diff --git a/tests/data/test.png b/tests/data/test.png deleted file mode 100644 index f1ffd4a..0000000 Binary files a/tests/data/test.png and /dev/null differ diff --git a/tests/test_commandline_export.py b/tests/test_commandline_export.py index 05ead77..6d7f068 100644 --- a/tests/test_commandline_export.py +++ b/tests/test_commandline_export.py @@ -1,14 +1,13 @@ import sys import os import tempfile -from avp.command import Command -from . import getTestDataPath +from . import command, getTestDataPath, MockSignal from pytestqt import qtbot -def test_commandline_classic_export(qtbot): +def test_commandline_classic_export(qtbot, command): """Run Qt event loop and create a video in the system /tmp or /temp""" - soundFile = getTestDataPath("test.ogg") + soundFile = getTestDataPath("inputfiles/test.ogg") outputDir = tempfile.mkdtemp(prefix="avp-test-") outputFilename = os.path.join(outputDir, "output.mp4") sys.argv = [ @@ -21,9 +20,6 @@ def test_commandline_classic_export(qtbot): "-o", outputFilename, ] - - command = Command() - command.quit = lambda _: None command.parseArgs() # Command object now has a video_thread Worker which is exporting the video diff --git a/tests/test_commandline_parser.py b/tests/test_commandline_parser.py index 77836ce..186c602 100644 --- a/tests/test_commandline_parser.py +++ b/tests/test_commandline_parser.py @@ -1,39 +1,34 @@ import sys import pytest -from avp.command import Command from pytestqt import qtbot +from . import command -def test_commandline_help(qtbot): - command = Command() +def test_commandline_help(qtbot, command): sys.argv = ["", "--help"] with pytest.raises(SystemExit): command.parseArgs() -def test_commandline_help_if_bad_args(qtbot): - command = Command() +def test_commandline_help_if_bad_args(qtbot, command): sys.argv = ["", "--junk"] with pytest.raises(SystemExit): command.parseArgs() -def test_commandline_launches_gui_if_verbose(qtbot): - command = Command() +def test_commandline_launches_gui_if_verbose(qtbot, command): sys.argv = ["", "--verbose"] mode = command.parseArgs() assert mode == "GUI" -def test_commandline_launches_gui_if_verbose_with_project(qtbot): - command = Command() +def test_commandline_launches_gui_if_verbose_with_project(qtbot, command): sys.argv = ["", "test", "--verbose"] mode = command.parseArgs() assert mode == "GUI" -def test_commandline_tries_to_export(qtbot): - command = Command() +def test_commandline_tries_to_export(qtbot, command): didCallFunction = False def captureFunction(*args): @@ -46,16 +41,13 @@ def test_commandline_tries_to_export(qtbot): assert didCallFunction -def test_commandline_parses_classic_by_alias(qtbot): - command = Command() +def test_commandline_parses_classic_by_alias(qtbot, command): assert command.parseCompName("original") == "Classic Visualizer" -def test_commandline_parses_conway_by_short_name(qtbot): - command = Command() +def test_commandline_parses_conway_by_short_name(qtbot, command): assert command.parseCompName("conway") == "Conway's Game of Life" -def test_commandline_parses_image_by_name(qtbot): - command = Command() +def test_commandline_parses_image_by_name(qtbot, command): assert command.parseCompName("image") == "Image" diff --git a/tests/test_comp_color.py b/tests/test_comp_color.py index 6b82e4c..48b07ff 100644 --- a/tests/test_comp_color.py +++ b/tests/test_comp_color.py @@ -1,13 +1,12 @@ from avp.command import Command from pytestqt import qtbot from pytest import fixture -from . import imageDataSum +from . import imageDataSum, command @fixture -def coreWithColorComp(qtbot): +def coreWithColorComp(qtbot, command): """Fixture providing a Command object with Color component added""" - command = Command() command.settings.setValue("outputHeight", 1080) command.settings.setValue("outputWidth", 1920) command.core.insertComponent(0, command.core.moduleIndexFor("Color"), command) diff --git a/tests/test_comp_image.py b/tests/test_comp_image.py index a4f05e1..c580d5a 100644 --- a/tests/test_comp_image.py +++ b/tests/test_comp_image.py @@ -1,16 +1,15 @@ from avp.command import Command from pytestqt import qtbot from pytest import fixture -from . import audioData, MockSignal, imageDataSum, getTestDataPath +from . import audioData, command, MockSignal, imageDataSum, getTestDataPath sampleSize = 1470 # 44100 / 30 = 1470 @fixture -def coreWithImageComp(qtbot): +def coreWithImageComp(qtbot, command): """Fixture providing a Command object with Image component added""" - command = Command() command.settings.setValue("outputHeight", 1080) command.settings.setValue("outputWidth", 1920) command.core.insertComponent(0, command.core.moduleIndexFor("Image"), command) @@ -20,7 +19,7 @@ def coreWithImageComp(qtbot): def test_comp_image_set_path(coreWithImageComp): "Set imagePath of Image component" comp = coreWithImageComp.selectedComponents[0] - comp.imagePath = getTestDataPath("test.jpg") + comp.imagePath = getTestDataPath("inputfiles/test.jpg") image = comp.previewRender() assert imageDataSum(image) == 463711601 @@ -28,7 +27,7 @@ def test_comp_image_set_path(coreWithImageComp): def test_comp_image_scale_50_1080p(coreWithImageComp): """Image component stretches image to 50% at 1080p""" comp = coreWithImageComp.selectedComponents[0] - comp.imagePath = getTestDataPath("test.jpg") + comp.imagePath = getTestDataPath("inputfiles/test.jpg") image = comp.previewRender() sum = imageDataSum(image) comp.page.spinBox_scale.setValue(50) @@ -38,7 +37,7 @@ def test_comp_image_scale_50_1080p(coreWithImageComp): def test_comp_image_scale_50_720p(coreWithImageComp): """Image component stretches image to 50% at 720p""" comp = coreWithImageComp.selectedComponents[0] - comp.imagePath = getTestDataPath("test.jpg") + comp.imagePath = getTestDataPath("inputfiles/test.jpg") comp.page.spinBox_scale.setValue(50) image = comp.previewRender() sum = imageDataSum(image) diff --git a/tests/test_comp_life.py b/tests/test_comp_life.py index ad78e52..3c02117 100644 --- a/tests/test_comp_life.py +++ b/tests/test_comp_life.py @@ -1,13 +1,12 @@ from avp.command import Command from pytestqt import qtbot from pytest import fixture -from . import imageDataSum +from . import imageDataSum, command @fixture -def coreWithLifeComp(qtbot): +def coreWithLifeComp(qtbot, command): """Fixture providing a Command object with Waveform component added""" - command = Command() command.settings.setValue("outputHeight", 1080) command.settings.setValue("outputWidth", 1920) command.core.insertComponent( diff --git a/tests/test_comp_original.py b/tests/test_comp_original.py index 6264644..8cd00a4 100644 --- a/tests/test_comp_original.py +++ b/tests/test_comp_original.py @@ -2,16 +2,15 @@ from avp.command import Command from avp.toolkit.visualizer import transformData from pytestqt import qtbot from pytest import fixture -from . import audioData, MockSignal, imageDataSum +from . import audioData, command, MockSignal, imageDataSum sampleSize = 1470 # 44100 / 30 = 1470 @fixture -def coreWithClassicComp(qtbot): +def coreWithClassicComp(qtbot, command): """Fixture providing a Command object with Classic Visualizer component added""" - command = Command() command.core.insertComponent( 0, command.core.moduleIndexFor("Classic Visualizer"), command ) diff --git a/tests/test_comp_spectrum.py b/tests/test_comp_spectrum.py index 44fb257..870185c 100644 --- a/tests/test_comp_spectrum.py +++ b/tests/test_comp_spectrum.py @@ -1,13 +1,12 @@ from avp.command import Command from pytestqt import qtbot from pytest import fixture -from . import imageDataSum +from . import imageDataSum, command @fixture -def coreWithSpectrumComp(qtbot): +def coreWithSpectrumComp(qtbot, command): """Fixture providing a Command object with Spectrum component added""" - command = Command() command.settings.setValue("outputHeight", 1080) command.settings.setValue("outputWidth", 1920) command.core.insertComponent(0, command.core.moduleIndexFor("Spectrum"), command) diff --git a/tests/test_comp_text.py b/tests/test_comp_text.py index e389ff9..20b202d 100644 --- a/tests/test_comp_text.py +++ b/tests/test_comp_text.py @@ -2,13 +2,12 @@ from avp.command import Command from PyQt6.QtGui import QFont from pytestqt import qtbot from pytest import fixture, mark -from . import audioData, MockSignal, imageDataSum +from . import audioData, command, MockSignal, imageDataSum @fixture -def coreWithTextComp(qtbot): +def coreWithTextComp(qtbot, command): """Fixture providing a Command object with Title Text component added""" - command = Command() command.core.insertComponent(0, command.core.moduleIndexFor("Title Text"), command) yield command.core diff --git a/tests/test_comp_waveform.py b/tests/test_comp_waveform.py index a71040b..eb5800d 100644 --- a/tests/test_comp_waveform.py +++ b/tests/test_comp_waveform.py @@ -1,12 +1,11 @@ -from avp.command import Command from pytestqt import qtbot from pytest import fixture +from . import command @fixture -def coreWithWaveformComp(qtbot): +def coreWithWaveformComp(qtbot, command): """Fixture providing a Command object with Waveform component added""" - command = Command() command.core.insertComponent(0, command.core.moduleIndexFor("Waveform"), command) yield command.core diff --git a/tests/test_mainwindow_projects.py b/tests/test_mainwindow_projects.py new file mode 100644 index 0000000..8ad491a --- /dev/null +++ b/tests/test_mainwindow_projects.py @@ -0,0 +1,40 @@ +from pytest import fixture +from pytestqt import qtbot +from . import getTestDataPath, window + + +def test_mainwindow_clear(qtbot, window): + """MainWindow.clear() gives us a clean slate""" + assert len(window.core.selectedComponents) == 0 + + +def test_mainwindow_openProject(qtbot, window): + """Open testproject.avp using MainWindow.openProject()""" + window.openProject(getTestDataPath("projects/testproject.avp"), prompt=False) + assert len(window.core.selectedComponents) == 2 + + +def test_mainwindow_newProject_without_unsaved_changes(qtbot, window): + """Starting new project without unsaved changes""" + didCallFunction = False + + def captureFunction(*args, **kwargs): + nonlocal didCallFunction + didCallFunction = True + + window.createNewProject(prompt=False) + assert not didCallFunction + assert len(window.core.selectedComponents) == 0 + + +def test_mainwindow_newProject_with_unsaved_changes(qtbot, window): + """Starting new project with unsaved changes""" + didCallFunction = False + + def captureFunction(*args, **kwargs): + nonlocal didCallFunction + didCallFunction = True + + window.openSaveChangesDialog = captureFunction + window.createNewProject(prompt=True) + assert didCallFunction diff --git a/tests/test_mainwindow_undostack.py b/tests/test_mainwindow_undostack.py index 1eec1ef..ceaf87e 100644 --- a/tests/test_mainwindow_undostack.py +++ b/tests/test_mainwindow_undostack.py @@ -1,16 +1,7 @@ from pytest import fixture from pytestqt import qtbot from avp.gui.mainwindow import MainWindow -from . import getTestDataPath - - -@fixture -def window(qtbot): - window = MainWindow(None, None) - qtbot.addWidget(window) - window.settings.setValue("outputWidth", 1920) - window.settings.setValue("outputHeight", 1080) - yield window +from . import getTestDataPath, window def test_undo_classic_visualizer_sensitivity(window, qtbot): @@ -20,7 +11,7 @@ def test_undo_classic_visualizer_sensitivity(window, qtbot): 0, window.core.moduleIndexFor("Classic Visualizer"), window ) comp = window.core.selectedComponents[0] - comp.imagePath = getTestDataPath("test.jpg") + comp.imagePath = getTestDataPath("inputfiles/test.jpg") for i in range(1, 100): comp.page.spinBox_scale.setValue(i) assert comp.scale == 99 @@ -32,7 +23,7 @@ def test_undo_image_scale(window, qtbot): """Undo Image component scale setting should undo multiple merged actions.""" window.core.insertComponent(0, window.core.moduleIndexFor("Image"), window) comp = window.core.selectedComponents[0] - comp.imagePath = getTestDataPath("test.jpg") + comp.imagePath = getTestDataPath("inputfiles/test.jpg") comp.page.spinBox_scale.setValue(100) for i in range(10, 401): comp.page.spinBox_scale.setValue(i) diff --git a/tests/test_toolkit_common.py b/tests/test_toolkit_common.py index 8e9dca2..b20ae53 100644 --- a/tests/test_toolkit_common.py +++ b/tests/test_toolkit_common.py @@ -2,20 +2,25 @@ from pytest import fixture from pytestqt import qtbot from avp.command import Command from avp.toolkit import blockSignals, rgbFromString +from . import command @fixture def gotWarning(): """Check if a function called log.warning""" import avp.toolkit.common as tk + warning = False + def gotWarning(): nonlocal warning return warning + class log: def warning(self, *args): nonlocal warning warning = True + oldLog = tk.log tk.log = log() try: @@ -24,8 +29,7 @@ def gotWarning(): tk.log = oldLog -def test_blockSignals(qtbot): - command = Command() +def test_blockSignals(qtbot, command): command.core.insertComponent(0, 0, command) comp = command.core.selectedComponents[0] assert comp.page.spinBox_scale.signalsBlocked() == False @@ -41,4 +45,4 @@ def test_rgbFromString(gotWarning): def test_rgbFromString_error(gotWarning): assert rgbFromString("255,255,256") == (255, 255, 255) - assert gotWarning() \ No newline at end of file + assert gotWarning() diff --git a/tests/test_toolkit_ffmpeg.py b/tests/test_toolkit_ffmpeg.py index b015470..363eba1 100644 --- a/tests/test_toolkit_ffmpeg.py +++ b/tests/test_toolkit_ffmpeg.py @@ -1,7 +1,8 @@ import pytest +from avp.core import Core from avp.command import Command from avp.toolkit.ffmpeg import createFfmpegCommand -from . import audioData +from . import audioData, getTestDataPath, initCore def test_readAudioFile_data(audioData): @@ -14,6 +15,7 @@ def test_readAudioFile_duration(audioData): @pytest.mark.parametrize("width, height", ((1920, 1080), (1280, 720))) def test_createFfmpegCommand(width, height): + initCore() command = Command() command.settings.setValue("outputWidth", width) command.settings.setValue("outputHeight", height) -- cgit v1.2.3