2019-01-13 11:36:44 +00:00
|
|
|
"""
|
|
|
|
- Test command-line parsing and Config generation.
|
|
|
|
- Integration tests (see conftest.py).
|
|
|
|
"""
|
2022-03-09 06:36:49 +00:00
|
|
|
import os.path
|
2018-08-20 04:42:43 +00:00
|
|
|
import shlex
|
2018-08-27 11:48:35 +00:00
|
|
|
from os.path import abspath
|
2018-08-20 07:26:51 +00:00
|
|
|
from pathlib import Path
|
2018-08-27 06:45:21 +00:00
|
|
|
from typing import TYPE_CHECKING, Callable
|
2019-01-17 04:55:09 +00:00
|
|
|
from unittest import mock
|
2018-08-20 04:42:43 +00:00
|
|
|
|
|
|
|
import click
|
|
|
|
import pytest
|
|
|
|
from click.testing import CliRunner
|
|
|
|
|
2018-12-20 10:31:55 +00:00
|
|
|
import corrscope.channel
|
|
|
|
from corrscope import cli
|
|
|
|
from corrscope.cli import YAML_NAME
|
|
|
|
from corrscope.config import yaml
|
2019-01-13 11:36:44 +00:00
|
|
|
from corrscope.corrscope import Arguments, Config, CorrScope
|
2018-12-20 10:31:55 +00:00
|
|
|
from corrscope.outputs import FFmpegOutputConfig
|
|
|
|
from corrscope.util import pushd
|
2018-08-27 11:15:50 +00:00
|
|
|
|
2018-08-20 04:42:43 +00:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
import pytest_mock
|
|
|
|
|
|
|
|
|
2018-08-27 11:15:50 +00:00
|
|
|
def call_main(argv):
|
2019-01-03 08:57:30 +00:00
|
|
|
return CliRunner().invoke(
|
|
|
|
cli.main, argv, catch_exceptions=False, standalone_mode=False
|
|
|
|
)
|
2018-08-20 04:42:43 +00:00
|
|
|
|
|
|
|
|
2018-12-20 10:31:55 +00:00
|
|
|
# corrscope configuration sinks
|
2018-08-20 04:42:43 +00:00
|
|
|
|
2019-01-03 08:57:30 +00:00
|
|
|
|
2019-01-17 04:55:09 +00:00
|
|
|
def yaml_sink(_mocker, command: str):
|
2021-06-14 22:09:24 +00:00
|
|
|
"""Mocks yaml.dump() and returns call args. Also tests dumping and loading."""
|
2019-01-17 04:55:09 +00:00
|
|
|
with mock.patch.object(yaml, "dump") as dump:
|
|
|
|
argv = shlex.split(command) + ["-w"]
|
|
|
|
call_main(argv)
|
2018-08-20 07:26:51 +00:00
|
|
|
|
2019-01-17 04:55:09 +00:00
|
|
|
dump.assert_called_once()
|
|
|
|
(cfg, stream), kwargs = dump.call_args
|
|
|
|
|
|
|
|
assert isinstance(cfg, Config)
|
|
|
|
|
|
|
|
yaml_dump = yaml.dump(cfg)
|
2019-01-09 00:21:46 +00:00
|
|
|
|
|
|
|
# YAML Representer.ignore_aliases() should return True for Enums.
|
|
|
|
# If it returns True for other types, "null" is dumped explicitly, which is ugly.
|
|
|
|
assert "end_time: null" not in yaml_dump
|
|
|
|
|
2019-01-17 04:55:09 +00:00
|
|
|
cfg_round_trip = yaml.load(yaml_dump)
|
|
|
|
assert cfg_round_trip == cfg, yaml_dump
|
2018-08-20 07:26:51 +00:00
|
|
|
|
2018-12-21 03:56:46 +00:00
|
|
|
return (cfg, stream)
|
2018-08-20 07:26:51 +00:00
|
|
|
|
|
|
|
|
2019-01-03 08:57:30 +00:00
|
|
|
def player_sink(mocker: "pytest_mock.MockFixture", command: str):
|
|
|
|
CorrScope = mocker.patch.object(cli, "CorrScope")
|
2018-08-20 07:26:51 +00:00
|
|
|
|
2019-01-03 08:57:30 +00:00
|
|
|
argv = shlex.split(command) + ["-p"]
|
2018-12-21 03:56:46 +00:00
|
|
|
call_main(argv)
|
2018-08-27 06:45:21 +00:00
|
|
|
|
2018-12-21 03:56:46 +00:00
|
|
|
CorrScope.assert_called_once()
|
|
|
|
args, kwargs = CorrScope.call_args
|
|
|
|
cfg = args[0]
|
2018-08-27 06:45:21 +00:00
|
|
|
|
2018-12-21 03:56:46 +00:00
|
|
|
assert isinstance(cfg, Config)
|
|
|
|
return (cfg,)
|
2018-08-20 07:26:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(params=[yaml_sink, player_sink])
|
2019-01-03 08:57:30 +00:00
|
|
|
def any_sink(request) -> Callable[["pytest_mock.MockFixture", str], tuple]:
|
2018-08-20 07:26:51 +00:00
|
|
|
sink = request.param
|
2018-12-21 03:56:46 +00:00
|
|
|
return sink
|
2018-08-20 04:42:43 +00:00
|
|
|
|
|
|
|
|
2018-12-20 10:31:55 +00:00
|
|
|
# corrscope configuration sources
|
2018-08-20 04:42:43 +00:00
|
|
|
|
2019-01-03 08:57:30 +00:00
|
|
|
|
2018-12-21 03:56:46 +00:00
|
|
|
def test_no_files(any_sink, mocker):
|
2018-08-20 04:42:43 +00:00
|
|
|
with pytest.raises(click.ClickException):
|
2019-01-03 08:57:30 +00:00
|
|
|
any_sink(mocker, "")
|
2018-08-20 07:26:51 +00:00
|
|
|
|
|
|
|
|
2019-01-03 08:57:30 +00:00
|
|
|
@pytest.mark.parametrize("wav_dir", ". tests".split())
|
2018-12-21 03:56:46 +00:00
|
|
|
def test_file_dirs(any_sink, mocker, wav_dir):
|
2021-06-14 22:09:24 +00:00
|
|
|
"""Ensure loading files from `dir` places `dir/*.wav` in config."""
|
2019-01-03 08:57:30 +00:00
|
|
|
wavs = Path(wav_dir).glob("*.wav")
|
2018-08-20 08:13:47 +00:00
|
|
|
wavs = sorted(str(x) for x in wavs)
|
2018-08-20 04:42:43 +00:00
|
|
|
|
2018-12-21 03:56:46 +00:00
|
|
|
cfg = any_sink(mocker, wav_dir)[0]
|
2018-08-20 07:26:51 +00:00
|
|
|
assert isinstance(cfg, Config)
|
2018-08-20 04:42:43 +00:00
|
|
|
|
2018-08-20 07:26:51 +00:00
|
|
|
assert [chan.wav_path for chan in cfg.channels] == wavs
|
2018-08-27 06:50:21 +00:00
|
|
|
|
|
|
|
|
2022-03-09 06:36:49 +00:00
|
|
|
@pytest.mark.parametrize("wav_dir", ". tests".split())
|
|
|
|
def test_absolute_dir(any_sink, mocker, wav_dir):
|
|
|
|
"""Ensure loading files from `abspath(dir)` functions properly."""
|
|
|
|
|
|
|
|
wavs = Path(wav_dir).glob("*.wav")
|
|
|
|
wavs = sorted(abspath(str(x)) for x in wavs)
|
|
|
|
|
|
|
|
cfg = any_sink(mocker, shlex.quote(abspath(wav_dir)))[0]
|
|
|
|
assert isinstance(cfg, Config)
|
|
|
|
|
|
|
|
assert [chan.wav_path for chan in cfg.channels] == wavs
|
|
|
|
|
|
|
|
|
|
|
|
def test_absolute_files(any_sink, mocker):
|
|
|
|
"""On Linux, the shell expands wildcards. Ensure loading absolute .wav paths
|
|
|
|
functions properly."""
|
|
|
|
|
|
|
|
wav_dir = "tests"
|
|
|
|
|
|
|
|
wavs = Path(wav_dir).glob("*.wav")
|
|
|
|
wavs = sorted(abspath(str(x)) for x in wavs)
|
|
|
|
|
|
|
|
cfg = any_sink(mocker, shlex.join(wavs))[0]
|
|
|
|
assert isinstance(cfg, Config)
|
|
|
|
|
|
|
|
assert [chan.wav_path for chan in cfg.channels] == wavs
|
|
|
|
|
|
|
|
|
|
|
|
def test_absolute_wildcard(any_sink, mocker):
|
|
|
|
"""On Windows, the program is expected to expand wildcards. Ensure loading
|
|
|
|
absolute paths containing wildcards functions properly."""
|
|
|
|
|
|
|
|
wav_dir = "tests"
|
|
|
|
|
|
|
|
wavs = Path(wav_dir).glob("*.wav")
|
|
|
|
wavs = sorted(abspath(str(x)) for x in wavs)
|
|
|
|
|
|
|
|
wildcard_wav = os.path.join(abspath(wav_dir), "*.wav")
|
|
|
|
cfg = any_sink(mocker, shlex.quote(wildcard_wav))[0]
|
|
|
|
assert isinstance(cfg, Config)
|
|
|
|
|
|
|
|
assert [chan.wav_path for chan in cfg.channels] == wavs
|
|
|
|
|
|
|
|
|
2018-09-03 23:37:47 +00:00
|
|
|
def q(path: Path) -> str:
|
|
|
|
return shlex.quote(str(path))
|
|
|
|
|
|
|
|
|
2018-12-21 03:56:46 +00:00
|
|
|
def test_write_dir(mocker):
|
2021-06-14 22:09:24 +00:00
|
|
|
"""Loading `--audio another/dir` should write YAML to current dir.
|
|
|
|
Writing YAML to audio dir: causes relative paths (relative to pwd) to break."""
|
2018-08-27 06:50:21 +00:00
|
|
|
|
2019-01-03 08:57:30 +00:00
|
|
|
audio_path = Path("tests/sine440.wav")
|
|
|
|
arg_str = f"tests -a {q(audio_path)}"
|
2018-08-27 06:50:21 +00:00
|
|
|
|
2019-01-03 08:57:30 +00:00
|
|
|
cfg, outpath = yaml_sink(mocker, arg_str) # type: Config, Path
|
2018-08-27 06:50:21 +00:00
|
|
|
assert isinstance(outpath, Path)
|
|
|
|
|
2018-08-27 08:27:26 +00:00
|
|
|
# Ensure YAML config written to current dir.
|
|
|
|
assert outpath.parent == Path()
|
|
|
|
assert outpath.name == str(outpath)
|
|
|
|
assert str(outpath) == audio_path.with_suffix(YAML_NAME).name
|
|
|
|
|
|
|
|
# Ensure config paths are valid.
|
|
|
|
assert outpath.parent / cfg.master_audio == audio_path
|
2018-08-27 11:15:50 +00:00
|
|
|
|
|
|
|
|
2019-01-13 11:36:44 +00:00
|
|
|
# Integration tests
|
|
|
|
|
|
|
|
|
2019-01-03 08:57:30 +00:00
|
|
|
@pytest.mark.usefixtures("Popen")
|
2018-12-21 03:56:46 +00:00
|
|
|
def test_load_yaml_another_dir(mocker, Popen):
|
2021-06-14 22:09:24 +00:00
|
|
|
"""YAML file located in `another/dir` should resolve `master_audio`, `channels[].
|
|
|
|
wav_path`, and video `path` from `another/dir`."""
|
2018-08-27 11:48:35 +00:00
|
|
|
|
2019-01-03 08:57:30 +00:00
|
|
|
subdir = "tests"
|
|
|
|
wav = "sine440.wav"
|
|
|
|
mp4 = "sine440.mp4"
|
2018-08-27 11:48:35 +00:00
|
|
|
with pushd(subdir):
|
2019-01-03 08:57:30 +00:00
|
|
|
arg_str = f"{wav} -a {wav}"
|
|
|
|
cfg, outpath = yaml_sink(mocker, arg_str) # type: Config, Path
|
2018-08-27 11:15:50 +00:00
|
|
|
|
2019-01-03 08:57:30 +00:00
|
|
|
cfg.begin_time = 100 # To skip all actual rendering
|
2018-08-27 11:48:35 +00:00
|
|
|
|
2018-12-20 10:31:55 +00:00
|
|
|
# Log execution of CorrScope().play()
|
2019-01-03 08:57:30 +00:00
|
|
|
Wave = mocker.spy(corrscope.channel, "Wave")
|
2018-10-31 07:32:43 +00:00
|
|
|
|
2021-06-30 13:28:07 +00:00
|
|
|
# Same function as used in cli.py and gui/__init__.py.
|
|
|
|
output = cfg.get_ffmpeg_cfg(mp4)
|
|
|
|
|
2018-12-20 10:31:55 +00:00
|
|
|
corr = CorrScope(cfg, Arguments(subdir, [output]))
|
|
|
|
corr.play()
|
2018-08-27 11:15:50 +00:00
|
|
|
|
2021-06-30 13:28:07 +00:00
|
|
|
# The .wav path (specified in Config) should be resolved relative to the config
|
|
|
|
# file.
|
2019-01-03 08:57:30 +00:00
|
|
|
wav_abs = abspath(f"{subdir}/{wav}")
|
2021-06-30 13:28:07 +00:00
|
|
|
|
|
|
|
# The output video path (specified in CLI --render) should be resolved relative to
|
|
|
|
# the shell's working directory.
|
|
|
|
mp4_abs = abspath(mp4)
|
2018-08-27 11:48:35 +00:00
|
|
|
|
2018-08-27 11:15:50 +00:00
|
|
|
# Test `wave_path`
|
|
|
|
args, kwargs = Wave.call_args
|
2019-01-08 22:28:42 +00:00
|
|
|
(wave_path,) = args
|
2018-08-27 11:48:35 +00:00
|
|
|
assert wave_path == wav_abs
|
2018-08-27 11:15:50 +00:00
|
|
|
|
|
|
|
# Test output `master_audio` and video `path`
|
|
|
|
args, kwargs = Popen.call_args
|
|
|
|
argv = args[0]
|
2018-08-27 11:48:35 +00:00
|
|
|
assert argv[-1] == mp4_abs
|
2019-01-03 08:57:30 +00:00
|
|
|
assert f"-i {wav_abs}" in " ".join(argv)
|