Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
* text=auto eol=crlf

# Preserve CRLF bytes for files covered by the repository's line-ending review policy.
.gitattributes -text
.github/workflows/*.yml -text
pytest.ini -text
requirements-test.txt -text
tests/*.py -text
36 changes: 36 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Tests

on:
pull_request:
push:
branches:
- master
workflow_dispatch:

permissions:
contents: read

jobs:
pytest:
name: pytest (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
python-version:
- "3.10"
- "3.13"
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
cache: pip
cache-dependency-path: requirements-test.txt
python-version: ${{ matrix.python-version }}
- name: Install test dependencies
run: python -m pip install --requirement requirements-test.txt
- name: Run tests
run: python -m pytest
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ desktop.ini
.idea/
.env
.vscode
.pytest_cache/
.coverage
htmlcov/

# internal-only math assets (keep local, do not commit)
src/assets_master_script/general_assets_internal.csv
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
addopts = -ra
testpaths = tests
4 changes: 4 additions & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
numpy
pandas
pytest
scipy
130 changes: 130 additions & 0 deletions tests/test_create_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from unittest.mock import Mock

import pandas as pd
import pytest

from src import create_input


ACR_COLUMNS = [
"rating_clips",
"math",
"pair_a",
"pair_b",
"trapping_clips",
"trapping_ans",
]


@pytest.fixture(autouse=True)
def clear_condition_cache():
"""
Clear the filename-to-condition cache around each test.

:return: None.
"""
create_input.file_to_condition_map.clear()
yield
create_input.file_to_condition_map.clear()


def test_conv_filename_to_condition_returns_sorted_groups():
"""
Verify that named condition groups are returned in sorted order.

:return: None.
"""
pattern = r"(?P<noise>[^_]+)_(?P<level>\d+)\.wav"

result = create_input.conv_filename_to_condition("white_10.wav", pattern)

assert list(result.items()) == [("level", "10"), ("noise", "white")]


def test_conv_filename_to_condition_marks_nonmatching_filename():
"""
Verify that a filename that does not match is marked as unknown.

:return: None.
"""
pattern = r"(?P<noise>[^_]+)_(?P<level>\d+)\.wav"

result = create_input.conv_filename_to_condition("unexpected.wav", pattern)

assert result == {"Unknown": "NoCondition"}


def test_validate_inputs_accepts_minimal_acr_columns():
"""
Verify that the required ACR input columns pass validation.

:return: None.
"""
cfg = {"number_of_gold_clips_per_session": "0"}
data = pd.DataFrame(columns=ACR_COLUMNS)

create_input.validate_inputs(cfg, data, "acr")


def test_validate_inputs_rejects_missing_required_column():
"""
Verify that validation identifies a missing required ACR column.

:return: None.
"""
cfg = {"number_of_gold_clips_per_session": "0"}
data = pd.DataFrame(columns=[column for column in ACR_COLUMNS if column != "pair_b"])

with pytest.raises(AssertionError, match="pair_b"):
create_input.validate_inputs(cfg, data, "acr")


def test_validate_inputs_requires_gold_columns_when_enabled():
"""
Verify that enabling gold clips requires their input columns.

:return: None.
"""
cfg = {"number_of_gold_clips_per_session": "1"}
data = pd.DataFrame(columns=ACR_COLUMNS)

with pytest.raises(AssertionError, match="gold_clips"):
create_input.validate_inputs(cfg, data, "acr")


def test_create_input_for_mturk_dispatches_acr(monkeypatch):
"""
Verify that ACR-compatible methods use the ACR input generator.

:param monkeypatch: Pytest fixture used to replace the input generator.
:return: None.
"""
cfg = object()
data = object()
output_path = "output.csv"
create_acr = Mock(return_value=3)
monkeypatch.setattr(create_input, "create_input_for_acr", create_acr)

result = create_input.create_input_for_mturk(cfg, data, "p835", output_path)

assert result == 3
create_acr.assert_called_once_with(cfg, data, output_path, "p835")


def test_create_input_for_mturk_dispatches_comparison_method(monkeypatch):
"""
Verify that comparison methods use the DCR/CCR input generator.

:param monkeypatch: Pytest fixture used to replace the input generator.
:return: None.
"""
cfg = object()
data = object()
output_path = "output.csv"
create_comparison = Mock(return_value=2)
monkeypatch.setattr(create_input, "create_input_for_dcrccr", create_comparison)

result = create_input.create_input_for_mturk(cfg, data, "ccr", output_path)

assert result == 2
create_comparison.assert_called_once_with(cfg, data, output_path)
36 changes: 36 additions & 0 deletions tests/test_result_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from src.result_parser import outliers_modified_z_score, outliers_z_score


def test_outliers_modified_z_score_removes_clear_outlier():
"""
Verify that the modified z-score filter removes a clear outlier.

:return: None.
"""
votes = [1, 2, 2, 2, 3, 100]

assert outliers_modified_z_score(votes) == [1, 2, 2, 2, 3]


def test_outliers_z_score_preserves_constant_votes():
"""
Verify that the z-score filter preserves votes with no variance.

:return: None.
"""
votes = [3, 3, 3]

assert outliers_z_score(votes) == votes


def test_outliers_z_score_uses_threshold():
"""
Verify that the z-score cutoff distinguishes values around the threshold.

:return: None.
"""
below_threshold = [10] * 10 + [1000]
above_threshold = [10] * 11 + [1000]

assert outliers_z_score(below_threshold) == below_threshold
assert outliers_z_score(above_threshold) == [10] * 11
Expand Down
Loading