Merge pull request #2 from thomasasfk/add-unit-tests

Add parametrized unit test coverage
pull/15/head
thomas 2023-03-20 15:49:24 +00:00 committed by GitHub
commit d320bd45f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 281 additions and 39 deletions

22
.github/workflows/pytest.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Run pytest
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
- name: Run pytest
run: pytest

1
.gitignore vendored
View File

@ -1 +1,2 @@
.idea
.pytest_cache

View File

@ -27,3 +27,10 @@ Install via the extensions tab on the [AUTOMATIC1111 webui](https://github.com/A
- i.e `25, 50, 75, 125, 150, 175, 200` `50, 125, 300` etc.
![settings.png](docs%2Fsettings.png)
## Contributing:
- Open to suggestions
- Pull requests are appreciated
- Write tests if possible and useful
- Run pre-commit!

View File

@ -0,0 +1,59 @@
def _display_multiplication(num):
return f'x{round(num / 100, 3)}'
def _display_raw_percentage(num):
return f'{num}%'
def _display_minus_and_plus(num):
num -= 100
if num > 0:
return f'+{num}%'
return f'{num}%'
_DEFAULT_DISPLAY_KEY = 'Incremental/decremental percentage (-50%, +50%)'
_PREDEFINED_PERCENTAGES_DISPLAY_MAP = {
_DEFAULT_DISPLAY_KEY: _display_minus_and_plus,
'Raw percentage (50%, 150%)': _display_raw_percentage,
'Multiplication (x0.5, x1.5)': _display_multiplication,
}
_MIN_DIMENSION = 64
_MAX_DIMENSION = 2048
def _scale_by_percentage(width, height, pct):
aspect_ratio = float(width) / float(height)
step = (pct - 1.0)
new_width = int(round(width * (1.0 + step)))
new_height = int(round(new_width / aspect_ratio))
return _clamp_to_boundaries(new_width, new_height, aspect_ratio)
def _scale_dimensions_to_max_dimension(width, height, max_dim):
aspect_ratio = float(width) / float(height)
if width > height:
new_width = max_dim
new_height = max(int(round(max_dim / aspect_ratio)), 1)
else:
new_height = max_dim
new_width = max(int(round(max_dim * aspect_ratio)), 1)
return _clamp_to_boundaries(new_width, new_height, aspect_ratio)
def _clamp_to_boundaries(width, height, aspect_ratio):
if width > _MAX_DIMENSION:
width = _MAX_DIMENSION
height = int(round(width / aspect_ratio))
if height > _MAX_DIMENSION:
height = _MAX_DIMENSION
width = int(round(height * aspect_ratio))
if width < _MIN_DIMENSION:
width = _MIN_DIMENSION
height = int(round(width / aspect_ratio))
if height < _MIN_DIMENSION:
height = _MIN_DIMENSION
width = int(round(height * aspect_ratio))
return width, height

View File

@ -7,8 +7,14 @@ from modules import scripts
from modules import shared
from modules.shared import opts
_MIN_DIMENSION = 64
_MAX_DIMENSION = 2048
from aspect_ratio_helper._util import _DEFAULT_DISPLAY_KEY
from aspect_ratio_helper._util import _MAX_DIMENSION
from aspect_ratio_helper._util import _MIN_DIMENSION
from aspect_ratio_helper._util import _PREDEFINED_PERCENTAGES_DISPLAY_MAP
from aspect_ratio_helper._util import _scale_by_percentage
from aspect_ratio_helper._util import _scale_dimensions_to_max_dimension
_EXTENSION_NAME = 'Aspect Ratio Helper'
@ -33,8 +39,14 @@ def on_ui_settings():
shared.opts.add_option(
key='arh_max_width_or_height',
info=shared.OptionInfo(
default=1024,
default=_MAX_DIMENSION / 2,
label='Maximum width or height default',
component=gr.Slider,
component_args={
'minimum': _MIN_DIMENSION,
'maximum': _MAX_DIMENSION,
'step': 1,
},
section=section,
),
)
@ -42,7 +54,7 @@ def on_ui_settings():
key='arh_show_predefined_percentages',
info=shared.OptionInfo(
default=True,
label='Show percentage buttons',
label='Show predefined percentage buttons',
section=section,
),
)
@ -50,39 +62,23 @@ def on_ui_settings():
key='arh_predefined_percentages',
info=shared.OptionInfo(
default='25, 50, 75, 125, 150, 175, 200',
label='Percentage buttons (75, 125, 150)',
label='Predefined percentage buttons, applied to dimensions (75, '
'125, 150)',
section=section,
),
)
shared.opts.add_option(
key='arh_predefined_percentages_display_key',
info=shared.OptionInfo(
default=_DEFAULT_DISPLAY_KEY,
label='Predefined percentage display format',
component=gr.Dropdown,
component_args=lambda: {
'choices': tuple(_PREDEFINED_PERCENTAGES_DISPLAY_MAP.keys()),
},
section=section,
),
)
def _scale_by_percentage(width, height, pct):
aspect_ratio = float(width) / float(height)
step = (pct - 1.0)
new_width = max(int(round(width * (1.0 + step))), 1)
new_height = max(int(round(new_width / aspect_ratio)), 1)
if new_width > _MAX_DIMENSION:
new_width = _MAX_DIMENSION
new_height = max(int(round(new_width / aspect_ratio)), 1)
if new_height > _MAX_DIMENSION:
new_height = _MAX_DIMENSION
new_width = max(int(round(new_height * aspect_ratio)), 1)
if new_width < _MIN_DIMENSION:
new_width = _MIN_DIMENSION
new_height = max(int(round(new_width / aspect_ratio)), 1)
if new_height < _MIN_DIMENSION:
new_height = _MIN_DIMENSION
new_width = max(int(round(new_height * aspect_ratio)), 1)
return new_width, new_height
def _scale_dimensions_to_max_dimension(width, height, max_dim):
if max_dim == max(width, height):
return width, height
aspect_ratio = float(width) / float(height)
if width > height:
return max_dim, max(int(round(max_dim / aspect_ratio)), 1)
return max(int(round(max_dim * aspect_ratio)), 1), max_dim
class AspectRatioStepScript(scripts.Script):
@ -113,9 +109,9 @@ class AspectRatioStepScript(scripts.Script):
if opts.arh_show_max_width_or_height:
with gr.Row():
max_dimension = gr.inputs.Slider(
minimum=64,
maximum=2048,
step=16,
minimum=_MIN_DIMENSION,
maximum=_MAX_DIMENSION,
step=1,
default=opts.arh_max_width_or_height,
label='Maximum width or height (whichever is higher)',
)
@ -126,13 +122,16 @@ class AspectRatioStepScript(scripts.Script):
)
if opts.arh_show_predefined_percentages:
display_func = _PREDEFINED_PERCENTAGES_DISPLAY_MAP.get(
opts.arh_predefined_percentages_display_key,
)
with gr.Column(variant='panel'), gr.Row(variant='compact'):
pps = opts.arh_predefined_percentages
percentages = [
int(x) for x in pps.split(',')
abs(int(x)) for x in pps.split(',')
]
for percentage in percentages:
gr.Button(value=f'{str(percentage)}%').click(
gr.Button(value=display_func(percentage)).click(
fn=partial(
_scale_by_percentage, pct=percentage / 100,
),

View File

@ -0,0 +1,3 @@
from aspect_ratio_helper.main import AspectRatioStepScript
__all__ = ['AspectRatioStepScript']

0
tests/__init__.py Normal file
View File

151
tests/_util_test.py Normal file
View File

@ -0,0 +1,151 @@
import pytest
from aspect_ratio_helper._util import _display_minus_and_plus
from aspect_ratio_helper._util import _display_multiplication
from aspect_ratio_helper._util import _display_raw_percentage
from aspect_ratio_helper._util import _MAX_DIMENSION
from aspect_ratio_helper._util import _MIN_DIMENSION
from aspect_ratio_helper._util import _scale_by_percentage
from aspect_ratio_helper._util import _scale_dimensions_to_max_dimension
@pytest.mark.parametrize(
'num, expected',
[
(50, 'x0.5'),
(150, 'x1.5'),
(175, 'x1.75'),
(250, 'x2.5'),
],
)
def test_display_multiplication(num, expected):
assert _display_multiplication(num) == expected
@pytest.mark.parametrize(
'num, expected',
[
(50, '50%'),
(75, '75%'),
(100, '100%'),
(150, '150%'),
(250, '250%'),
],
)
def test_display_raw_percentage(num, expected):
assert _display_raw_percentage(num) == expected
@pytest.mark.parametrize(
'num, expected_output', [
(150, '+50%'),
(100, '0%'),
(50, '-50%'),
(0, '-100%'),
(200, '+100%'),
(75, '-25%'),
],
)
def test_display_minus_and_plus(num, expected_output):
assert _display_minus_and_plus(num) == expected_output
@pytest.mark.parametrize(
'width, height, pct, expected',
[
pytest.param(200, 400, 0.5, (100, 200), id='50_percent_scale_down'),
pytest.param(100, 200, 2.0, (200, 400), id='200_percent_scale_up'),
pytest.param(100, 200, 1.1, (110, 220), id='10_percent_scale_up'),
pytest.param(100, 200, 0.9, (90, 180), id='10_percent_scale_down'),
pytest.param(100, 200, 0.0, (64, 128), id='scale_full_down'),
pytest.param(
_MIN_DIMENSION - 1,
_MIN_DIMENSION - 1,
0.5,
(_MIN_DIMENSION, _MIN_DIMENSION),
id='scale_below_min_dimension',
),
pytest.param(
_MAX_DIMENSION + 1,
_MAX_DIMENSION + 1,
2.0,
(_MAX_DIMENSION, _MAX_DIMENSION),
id='scale_above_max_dimension',
),
],
)
def test_scale_by_percentage(
width, height, pct, expected,
):
assert _scale_by_percentage(
width, height, pct,
) == expected
@pytest.mark.parametrize(
'width, height, max_dim, expected',
[
pytest.param(
100, 200, 400, (200, 400),
id='scale_up_to_max_dimension_horizontally',
),
pytest.param(
200, 100, 400, (400, 200),
id='scale_up_to_max_dimension_vertically',
),
pytest.param(
400, 64, 400, (400, 64),
id='no_scale_up_needed_with_max_dimension_width',
),
pytest.param(
64, 400, 400, (64, 400),
id='no_scale_up_needed_with_max_dimension_height',
),
pytest.param(
_MIN_DIMENSION, _MIN_DIMENSION, _MAX_DIMENSION,
(_MAX_DIMENSION, _MAX_DIMENSION),
id='scale_from_min_to_max',
),
pytest.param(
_MAX_DIMENSION, _MAX_DIMENSION, _MIN_DIMENSION,
(_MIN_DIMENSION, _MIN_DIMENSION),
id='scale_from_max_to_min',
),
pytest.param(
_MIN_DIMENSION, 32, _MIN_DIMENSION,
(128, _MIN_DIMENSION),
id='scale_below_min_height_dimension_clamps_retains_ar',
),
pytest.param(
32, _MIN_DIMENSION, _MIN_DIMENSION,
(_MIN_DIMENSION, 128),
id='scale_below_min_width_dimension_clamps_retains_ar',
),
pytest.param(
_MAX_DIMENSION, 4096, _MAX_DIMENSION,
(1024, _MAX_DIMENSION),
id='scale_above_max_height_dimension_clamps_retains_ar',
),
pytest.param(
4096, _MAX_DIMENSION, _MAX_DIMENSION,
(_MAX_DIMENSION, 1024),
id='scale_above_max_width_dimension_clamps_retains_ar',
),
pytest.param(
64, 64, _MIN_DIMENSION - 1,
(_MIN_DIMENSION, _MIN_DIMENSION),
id='scale_dimension_below_min_dimension_clamps_retains_ar',
),
pytest.param(
64, 64, _MAX_DIMENSION + 1,
(_MAX_DIMENSION, _MAX_DIMENSION),
id='scale_dimension_above_max_dimension_clamps_retains_ar',
),
],
)
def test_scale_dimensions_to_max_dimension(
width, height, max_dim, expected,
):
assert _scale_dimensions_to_max_dimension(
width, height, max_dim,
) == expected