2023-05-04 04:45:52 +00:00
|
|
|
import os
|
2023-09-08 01:17:43 +00:00
|
|
|
from pathlib import Path
|
2023-08-17 22:45:25 +00:00
|
|
|
from typing import Any
|
2023-05-04 04:45:52 +00:00
|
|
|
|
2023-08-17 22:45:25 +00:00
|
|
|
import pytest
|
2023-05-04 04:45:52 +00:00
|
|
|
from omegaconf import OmegaConf
|
2023-09-08 01:17:43 +00:00
|
|
|
from pydantic import ValidationError
|
2023-05-04 04:45:52 +00:00
|
|
|
|
2023-08-20 19:27:51 +00:00
|
|
|
|
2023-08-17 22:45:25 +00:00
|
|
|
@pytest.fixture
|
|
|
|
def patch_rootdir(tmp_path: Path, monkeypatch: Any) -> None:
|
|
|
|
"""This may be overkill since the current tests don't need the root dir to exist"""
|
|
|
|
monkeypatch.setenv("INVOKEAI_ROOT", str(tmp_path))
|
|
|
|
|
2023-05-25 03:57:15 +00:00
|
|
|
|
2023-05-04 04:45:52 +00:00
|
|
|
init1 = OmegaConf.create(
|
2023-07-27 14:54:01 +00:00
|
|
|
"""
|
2023-05-17 16:19:19 +00:00
|
|
|
InvokeAI:
|
|
|
|
Features:
|
2023-07-26 11:29:33 +00:00
|
|
|
always_use_cpu: false
|
2023-05-17 16:19:19 +00:00
|
|
|
Memory/Performance:
|
2023-07-11 21:37:02 +00:00
|
|
|
max_cache_size: 5
|
2023-07-26 11:29:33 +00:00
|
|
|
tiled_decode: false
|
2023-07-27 14:54:01 +00:00
|
|
|
"""
|
2023-05-04 04:45:52 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
init2 = OmegaConf.create(
|
2023-07-27 14:54:01 +00:00
|
|
|
"""
|
2023-05-17 16:19:19 +00:00
|
|
|
InvokeAI:
|
|
|
|
Features:
|
2023-07-26 11:29:33 +00:00
|
|
|
always_use_cpu: true
|
2023-05-17 16:19:19 +00:00
|
|
|
Memory/Performance:
|
2023-07-11 21:37:02 +00:00
|
|
|
max_cache_size: 2
|
2023-07-26 11:29:33 +00:00
|
|
|
tiled_decode: true
|
2023-07-27 14:54:01 +00:00
|
|
|
"""
|
2023-05-04 04:45:52 +00:00
|
|
|
)
|
|
|
|
|
2023-08-17 17:47:26 +00:00
|
|
|
init3 = OmegaConf.create(
|
|
|
|
"""
|
|
|
|
InvokeAI:
|
|
|
|
Generation:
|
|
|
|
sequential_guidance: true
|
|
|
|
attention_type: xformers
|
|
|
|
attention_slice_size: 7
|
|
|
|
forced_tiled_decode: True
|
|
|
|
Device:
|
|
|
|
device: cpu
|
2023-08-17 18:32:05 +00:00
|
|
|
Model Cache:
|
2023-08-17 17:47:26 +00:00
|
|
|
ram: 1.25
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
|
2023-07-27 14:54:01 +00:00
|
|
|
|
2023-08-17 22:45:25 +00:00
|
|
|
def test_use_init(patch_rootdir):
|
2023-05-04 04:45:52 +00:00
|
|
|
# note that we explicitly set omegaconf dict and argv here
|
|
|
|
# so that the values aren't read from ~invokeai/invokeai.yaml and
|
|
|
|
# sys.argv respectively.
|
2023-08-17 22:45:25 +00:00
|
|
|
from invokeai.app.services.config import InvokeAIAppConfig
|
|
|
|
|
2023-05-26 00:41:26 +00:00
|
|
|
conf1 = InvokeAIAppConfig.get_config()
|
2023-05-04 04:45:52 +00:00
|
|
|
assert conf1
|
2023-06-20 17:22:39 +00:00
|
|
|
conf1.parse_args(conf=init1, argv=[])
|
2023-07-26 11:29:33 +00:00
|
|
|
assert not conf1.tiled_decode
|
2023-07-11 21:37:02 +00:00
|
|
|
assert conf1.max_cache_size == 5
|
2023-07-26 11:29:33 +00:00
|
|
|
assert not conf1.always_use_cpu
|
2023-05-04 04:45:52 +00:00
|
|
|
|
2023-05-26 00:41:26 +00:00
|
|
|
conf2 = InvokeAIAppConfig.get_config()
|
2023-05-04 04:45:52 +00:00
|
|
|
assert conf2
|
2023-06-20 17:22:39 +00:00
|
|
|
conf2.parse_args(conf=init2, argv=[])
|
2023-07-26 11:29:33 +00:00
|
|
|
assert conf2.tiled_decode
|
2023-07-11 21:37:02 +00:00
|
|
|
assert conf2.max_cache_size == 2
|
2023-05-04 04:45:52 +00:00
|
|
|
assert not hasattr(conf2, "invalid_attribute")
|
2023-07-27 14:54:01 +00:00
|
|
|
|
|
|
|
|
2023-08-17 17:47:26 +00:00
|
|
|
def test_legacy():
|
2023-09-11 13:57:41 +00:00
|
|
|
from invokeai.app.services.config import InvokeAIAppConfig
|
|
|
|
|
2023-08-17 17:47:26 +00:00
|
|
|
conf = InvokeAIAppConfig.get_config()
|
|
|
|
assert conf
|
|
|
|
conf.parse_args(conf=init3, argv=[])
|
|
|
|
assert conf.xformers_enabled
|
|
|
|
assert conf.device == "cpu"
|
|
|
|
assert conf.use_cpu
|
2023-08-17 18:32:05 +00:00
|
|
|
assert conf.ram == 1.25
|
2023-08-17 17:47:26 +00:00
|
|
|
assert conf.ram_cache_size == 1.25
|
|
|
|
|
|
|
|
|
2023-05-04 04:45:52 +00:00
|
|
|
def test_argv_override():
|
2023-09-11 13:57:41 +00:00
|
|
|
from invokeai.app.services.config import InvokeAIAppConfig
|
|
|
|
|
2023-05-26 00:41:26 +00:00
|
|
|
conf = InvokeAIAppConfig.get_config()
|
2023-07-26 11:29:33 +00:00
|
|
|
conf.parse_args(conf=init1, argv=["--always_use_cpu", "--max_cache=10"])
|
|
|
|
assert conf.always_use_cpu
|
2023-07-11 21:37:02 +00:00
|
|
|
assert conf.max_cache_size == 10
|
2023-05-04 04:45:52 +00:00
|
|
|
assert conf.outdir == Path("outputs") # this is the default
|
2023-07-27 14:54:01 +00:00
|
|
|
|
|
|
|
|
2023-08-17 22:45:25 +00:00
|
|
|
def test_env_override(patch_rootdir):
|
|
|
|
from invokeai.app.services.config import InvokeAIAppConfig
|
|
|
|
|
2023-05-04 04:45:52 +00:00
|
|
|
# argv overrides
|
2023-05-26 00:41:26 +00:00
|
|
|
conf = InvokeAIAppConfig()
|
2023-07-11 21:37:02 +00:00
|
|
|
conf.parse_args(conf=init1, argv=["--max_cache=10"])
|
2023-08-17 22:45:25 +00:00
|
|
|
assert conf.always_use_cpu is False
|
2023-07-26 11:29:33 +00:00
|
|
|
os.environ["INVOKEAI_always_use_cpu"] = "True"
|
2023-07-11 21:37:02 +00:00
|
|
|
conf.parse_args(conf=init1, argv=["--max_cache=10"])
|
2023-08-17 22:45:25 +00:00
|
|
|
assert conf.always_use_cpu is True
|
2023-05-04 04:45:52 +00:00
|
|
|
|
2023-05-18 14:48:23 +00:00
|
|
|
# environment variables should be case insensitive
|
2023-07-11 21:37:02 +00:00
|
|
|
os.environ["InvokeAI_Max_Cache_Size"] = "15"
|
2023-05-26 00:41:26 +00:00
|
|
|
conf = InvokeAIAppConfig()
|
2023-06-20 17:22:39 +00:00
|
|
|
conf.parse_args(conf=init1, argv=[])
|
2023-07-11 21:37:02 +00:00
|
|
|
assert conf.max_cache_size == 15
|
2023-05-18 14:48:23 +00:00
|
|
|
|
2023-05-26 00:41:26 +00:00
|
|
|
conf = InvokeAIAppConfig()
|
2023-07-26 11:29:33 +00:00
|
|
|
conf.parse_args(conf=init1, argv=["--no-always_use_cpu", "--max_cache=10"])
|
2023-08-17 22:45:25 +00:00
|
|
|
assert conf.always_use_cpu is False
|
2023-07-11 21:37:02 +00:00
|
|
|
assert conf.max_cache_size == 10
|
2023-05-04 04:45:52 +00:00
|
|
|
|
2023-07-11 21:37:02 +00:00
|
|
|
conf = InvokeAIAppConfig.get_config(max_cache_size=20)
|
2023-05-26 00:41:26 +00:00
|
|
|
conf.parse_args(conf=init1, argv=[])
|
2023-07-11 21:37:02 +00:00
|
|
|
assert conf.max_cache_size == 20
|
2023-07-27 14:54:01 +00:00
|
|
|
|
2023-09-21 16:43:34 +00:00
|
|
|
# make sure that prefix is respected
|
|
|
|
del os.environ["INVOKEAI_always_use_cpu"]
|
|
|
|
os.environ["always_use_cpu"] = "True"
|
|
|
|
conf.parse_args(conf=init1, argv=[])
|
|
|
|
assert conf.always_use_cpu is False
|
|
|
|
|
2023-05-04 04:45:52 +00:00
|
|
|
|
2023-08-17 22:45:25 +00:00
|
|
|
def test_root_resists_cwd(patch_rootdir):
|
|
|
|
from invokeai.app.services.config import InvokeAIAppConfig
|
|
|
|
|
2023-08-01 02:27:07 +00:00
|
|
|
previous = os.environ["INVOKEAI_ROOT"]
|
|
|
|
cwd = Path(os.getcwd()).resolve()
|
|
|
|
|
|
|
|
os.environ["INVOKEAI_ROOT"] = "."
|
|
|
|
conf = InvokeAIAppConfig.get_config()
|
|
|
|
conf.parse_args([])
|
|
|
|
assert conf.root_path == cwd
|
|
|
|
|
2023-08-01 04:41:35 +00:00
|
|
|
os.chdir("..")
|
2023-08-01 02:27:07 +00:00
|
|
|
assert conf.root_path == cwd
|
|
|
|
os.environ["INVOKEAI_ROOT"] = previous
|
2023-08-01 04:41:35 +00:00
|
|
|
os.chdir(cwd)
|
2023-08-01 02:27:07 +00:00
|
|
|
|
|
|
|
|
2023-08-17 22:45:25 +00:00
|
|
|
def test_type_coercion(patch_rootdir):
|
|
|
|
from invokeai.app.services.config import InvokeAIAppConfig
|
|
|
|
|
2023-05-26 00:41:26 +00:00
|
|
|
conf = InvokeAIAppConfig().get_config()
|
|
|
|
conf.parse_args(argv=["--root=/tmp/foobar"])
|
2023-05-04 04:45:52 +00:00
|
|
|
assert conf.root == Path("/tmp/foobar")
|
|
|
|
assert isinstance(conf.root, Path)
|
2023-05-26 00:41:26 +00:00
|
|
|
conf = InvokeAIAppConfig.get_config(root="/tmp/different")
|
|
|
|
conf.parse_args(argv=["--root=/tmp/foobar"])
|
2023-05-04 04:45:52 +00:00
|
|
|
assert conf.root == Path("/tmp/different")
|
|
|
|
assert isinstance(conf.root, Path)
|
2023-09-08 00:41:00 +00:00
|
|
|
|
|
|
|
|
2023-09-08 01:17:43 +00:00
|
|
|
@pytest.mark.xfail(
|
|
|
|
reason="""
|
|
|
|
This test fails when run as part of the full test suite.
|
|
|
|
|
|
|
|
This test needs to deny nodes from being included in the InvocationsUnion by providing
|
|
|
|
an app configuration as a test fixture. Pytest executes all test files before running
|
2023-09-08 01:25:51 +00:00
|
|
|
tests, so the app configuration is already initialized by the time this test runs, and
|
|
|
|
the InvocationUnion is already created and the denied nodes are not omitted from it.
|
2023-09-08 01:17:43 +00:00
|
|
|
|
|
|
|
This test passes when `test_config.py` is tested in isolation.
|
|
|
|
|
|
|
|
Perhaps a solution would be to call `InvokeAIAppConfig.get_config().parse_args()` in
|
|
|
|
other test files?
|
|
|
|
"""
|
|
|
|
)
|
2023-09-08 00:41:00 +00:00
|
|
|
def test_deny_nodes(patch_rootdir):
|
2023-08-18 14:57:18 +00:00
|
|
|
from invokeai.app.services.config import InvokeAIAppConfig
|
2023-09-11 13:57:41 +00:00
|
|
|
|
2023-09-08 00:41:00 +00:00
|
|
|
# Allow integer, string and float, but explicitly deny float
|
|
|
|
allow_deny_nodes_conf = OmegaConf.create(
|
|
|
|
"""
|
|
|
|
InvokeAI:
|
|
|
|
Nodes:
|
|
|
|
allow_nodes:
|
|
|
|
- integer
|
|
|
|
- string
|
|
|
|
- float
|
|
|
|
deny_nodes:
|
|
|
|
- float
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
# must parse config before importing Graph, so its nodes union uses the config
|
|
|
|
conf = InvokeAIAppConfig().get_config()
|
|
|
|
conf.parse_args(conf=allow_deny_nodes_conf, argv=[])
|
2023-10-11 00:54:07 +00:00
|
|
|
from invokeai.app.services.shared.graph import Graph
|
2023-09-08 00:41:00 +00:00
|
|
|
|
|
|
|
# confirm graph validation fails when using denied node
|
|
|
|
Graph(nodes={"1": {"id": "1", "type": "integer"}})
|
|
|
|
Graph(nodes={"1": {"id": "1", "type": "string"}})
|
|
|
|
|
|
|
|
with pytest.raises(ValidationError):
|
|
|
|
Graph(nodes={"1": {"id": "1", "type": "float"}})
|
|
|
|
|
|
|
|
from invokeai.app.invocations.baseinvocation import BaseInvocation
|
|
|
|
|
|
|
|
# confirm invocations union will not have denied nodes
|
|
|
|
all_invocations = BaseInvocation.get_invocations()
|
|
|
|
|
|
|
|
has_integer = len([i for i in all_invocations if i.__fields__.get("type").default == "integer"]) == 1
|
|
|
|
has_string = len([i for i in all_invocations if i.__fields__.get("type").default == "string"]) == 1
|
|
|
|
has_float = len([i for i in all_invocations if i.__fields__.get("type").default == "float"]) == 1
|
|
|
|
|
|
|
|
assert has_integer
|
|
|
|
assert has_string
|
|
|
|
assert not has_float
|