From ca06eb08ddde3f267a0ce59c8f32b22f507a209a Mon Sep 17 00:00:00 2001 From: huchenlei Date: Fri, 3 Nov 2023 17:48:18 -0400 Subject: [PATCH 1/9] :wrench: setup CI --- .github/workflows/tests.yml | 68 +++++++++++++++++++++++++++++++------ tests/utils.py | 16 +++++++-- 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c190bdf39..4a6099a25 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,17 +10,15 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@v3 - with: + with: repository: 'AUTOMATIC1111/stable-diffusion-webui' path: 'stable-diffusion-webui' - ref: '5ab7f213bec2f816f9c5644becb32eb72c8ffb89' - + ref: '4afaaf8a020c1df457bcf7250cb1c7f609699fa7' - name: Checkout Code uses: actions/checkout@v3 with: repository: 'Mikubill/sd-webui-controlnet' - path: 'stable-diffusion-webui/extensions/sd-webui-controlnet' - + path: 'stable-diffusion-webui/extensions/sd-webui-controlnet' - name: Set up Python 3.10 uses: actions/setup-python@v4 with: @@ -28,10 +26,58 @@ jobs: cache: pip cache-dependency-path: | **/requirements*txt - stable-diffusion-webui/requirements*txt - - - run: | - pip install torch torchvision + launch.py + - name: Install test dependencies + run: pip install wait-for-it -r requirements-test.txt + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_PROGRESS_BAR: "off" + - name: Setup environment + run: python launch.py --skip-torch-cuda-test --exit + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_PROGRESS_BAR: "off" + TORCH_INDEX_URL: https://download.pytorch.org/whl/cpu + WEBUI_LAUNCH_LIVE_OUTPUT: "1" + PYTHONUNBUFFERED: "1" + - name: Download controlnet model for testing + run: | curl -Lo stable-diffusion-webui/extensions/sd-webui-controlnet/models/control_canny-fp16.safetensors https://huggingface.co/webui/ControlNet-modules-safetensors/resolve/main/control_canny-fp16.safetensors - cd stable-diffusion-webui && python launch.py --no-half --disable-opt-split-attention --use-cpu all --skip-torch-cuda-test --api --tests ./extensions/sd-webui-controlnet/tests - rm -fr stable-diffusion-webui/extensions/sd-webui-controlnet/models/control_canny-fp16.safetensors + - name: Start test server + run: > + python -m coverage run + --data-file=.coverage.server + launch.py + --skip-prepare-environment + --skip-torch-cuda-test + --test-server + --do-not-download-clip + --no-half + --disable-opt-split-attention + --use-cpu all + --api-server-stop + 2>&1 | tee output.txt & + - name: Run tests + run: | + wait-for-it --service 127.0.0.1:7860 -t 600 + python -m pytest -vv --junitxml=test/results.xml --cov . --cov-report=xml --verify-base-url ./extensions/sd-webui-controlnet/tests + - name: Kill test server + if: always() + run: curl -vv -XPOST http://127.0.0.1:7860/sdapi/v1/server-stop && sleep 10 + - name: Show coverage + run: | + python -m coverage combine .coverage* + python -m coverage report -i + python -m coverage html -i + - name: Upload main app output + uses: actions/upload-artifact@v3 + if: always() + with: + name: output + path: output.txt + - name: Upload coverage HTML + uses: actions/upload-artifact@v3 + if: always() + with: + name: htmlcov + path: htmlcov diff --git a/tests/utils.py b/tests/utils.py index b0595a0e2..c4a95b3f3 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,6 +2,7 @@ import sys import cv2 from base64 import b64encode +from pathlib import Path import requests @@ -9,9 +10,18 @@ def setup_test_env(): - ext_root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) - if ext_root not in sys.path: - sys.path.append(ext_root) + os.environ['IGNORE_CMD_ARGS_ERRORS'] = 'True' + + file_path = Path(__file__).resolve() + ext_root = file_path.parent.parent + a1111_root = ext_root.parent.parent + + for p in (ext_root, a1111_root): + if p not in sys.path: + sys.path.append(str(p)) + + # Initialize shared opts. + import webui def readImage(path): From 85e88b4bc3383f1d9da2c0a5a600b8e36f0785d0 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Fri, 3 Nov 2023 17:59:01 -0400 Subject: [PATCH 2/9] :bug: Fix path --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4a6099a25..a15a6958a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,7 +28,9 @@ jobs: **/requirements*txt launch.py - name: Install test dependencies - run: pip install wait-for-it -r requirements-test.txt + run: | + pip install wait-for-it + pip install -r stable-diffusion-webui/requirements-test.txt env: PIP_DISABLE_PIP_VERSION_CHECK: "1" PIP_PROGRESS_BAR: "off" From 015763023222ebaa905848485e7b680b82da2bb2 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Fri, 3 Nov 2023 20:11:08 -0400 Subject: [PATCH 3/9] cd --- .github/workflows/tests.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a15a6958a..af105c924 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,9 @@ jobs: uses: actions/checkout@v3 with: repository: 'Mikubill/sd-webui-controlnet' - path: 'stable-diffusion-webui/extensions/sd-webui-controlnet' + path: 'stable-diffusion-webui/extensions/sd-webui-controlnet' + - name: Step to A1111 directory + run: cd stable-diffusion-webui - name: Set up Python 3.10 uses: actions/setup-python@v4 with: @@ -30,7 +32,7 @@ jobs: - name: Install test dependencies run: | pip install wait-for-it - pip install -r stable-diffusion-webui/requirements-test.txt + pip install -r requirements-test.txt env: PIP_DISABLE_PIP_VERSION_CHECK: "1" PIP_PROGRESS_BAR: "off" @@ -44,7 +46,7 @@ jobs: PYTHONUNBUFFERED: "1" - name: Download controlnet model for testing run: | - curl -Lo stable-diffusion-webui/extensions/sd-webui-controlnet/models/control_canny-fp16.safetensors https://huggingface.co/webui/ControlNet-modules-safetensors/resolve/main/control_canny-fp16.safetensors + curl -Lo extensions/sd-webui-controlnet/models/control_canny-fp16.safetensors https://huggingface.co/webui/ControlNet-modules-safetensors/resolve/main/control_canny-fp16.safetensors - name: Start test server run: > python -m coverage run From 1851d874005b52842ec667d2862373d5c189a6d2 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Fri, 3 Nov 2023 20:16:51 -0400 Subject: [PATCH 4/9] set working dir --- .github/workflows/tests.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index af105c924..d9dbd6a76 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,8 +19,6 @@ jobs: with: repository: 'Mikubill/sd-webui-controlnet' path: 'stable-diffusion-webui/extensions/sd-webui-controlnet' - - name: Step to A1111 directory - run: cd stable-diffusion-webui - name: Set up Python 3.10 uses: actions/setup-python@v4 with: @@ -33,11 +31,13 @@ jobs: run: | pip install wait-for-it pip install -r requirements-test.txt + working-directory: stable-diffusion-webui env: PIP_DISABLE_PIP_VERSION_CHECK: "1" PIP_PROGRESS_BAR: "off" - name: Setup environment run: python launch.py --skip-torch-cuda-test --exit + working-directory: stable-diffusion-webui env: PIP_DISABLE_PIP_VERSION_CHECK: "1" PIP_PROGRESS_BAR: "off" @@ -47,6 +47,7 @@ jobs: - name: Download controlnet model for testing run: | curl -Lo extensions/sd-webui-controlnet/models/control_canny-fp16.safetensors https://huggingface.co/webui/ControlNet-modules-safetensors/resolve/main/control_canny-fp16.safetensors + working-directory: stable-diffusion-webui - name: Start test server run: > python -m coverage run @@ -61,10 +62,12 @@ jobs: --use-cpu all --api-server-stop 2>&1 | tee output.txt & + working-directory: stable-diffusion-webui - name: Run tests run: | wait-for-it --service 127.0.0.1:7860 -t 600 python -m pytest -vv --junitxml=test/results.xml --cov . --cov-report=xml --verify-base-url ./extensions/sd-webui-controlnet/tests + working-directory: stable-diffusion-webui - name: Kill test server if: always() run: curl -vv -XPOST http://127.0.0.1:7860/sdapi/v1/server-stop && sleep 10 @@ -73,6 +76,7 @@ jobs: python -m coverage combine .coverage* python -m coverage report -i python -m coverage html -i + working-directory: stable-diffusion-webui - name: Upload main app output uses: actions/upload-artifact@v3 if: always() From dd3545daa809c7784986f56d3ff772eabf54f57f Mon Sep 17 00:00:00 2001 From: huchenlei Date: Fri, 3 Nov 2023 22:36:32 -0400 Subject: [PATCH 5/9] fix openpose tests --- .../openpose_tests/openpose_e2e_test.py | 3 ++- tests/images/expected_woman_dw_all_output.png | Bin 15248 -> 15282 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/annotator_tests/openpose_tests/openpose_e2e_test.py b/tests/annotator_tests/openpose_tests/openpose_e2e_test.py index b078a15bc..9bc8ef5b3 100644 --- a/tests/annotator_tests/openpose_tests/openpose_e2e_test.py +++ b/tests/annotator_tests/openpose_tests/openpose_e2e_test.py @@ -1,6 +1,7 @@ import unittest import cv2 import numpy as np +from pathlib import Path from typing import Dict @@ -11,7 +12,7 @@ from annotator.openpose import OpenposeDetector class TestOpenposeDetector(unittest.TestCase): - image_path = './tests/images' + image_path = str(Path(__file__).parent.parent.parent / 'images') def setUp(self) -> None: self.detector = OpenposeDetector() self.detector.load_model() diff --git a/tests/images/expected_woman_dw_all_output.png b/tests/images/expected_woman_dw_all_output.png index 2bcefcc5a203606120711479f1bf8a2304588356..4ab98be7ce1710b7539e79bbf4d9374e8f7ed66f 100644 GIT binary patch literal 15282 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 T0sNa=0Rg-1j?`=0{1X2Mr3jj` From 4400f9988ab0d2b8da57b3fadcb9191f462d5ec4 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Fri, 3 Nov 2023 23:33:11 -0400 Subject: [PATCH 6/9] Fix web_api tests --- tests/utils.py | 23 +++++++++++++++-------- tests/web_api/img2img_test.py | 7 ------- tests/web_api/txt2img_test.py | 7 ------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index c4a95b3f3..0e3013599 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -31,16 +31,23 @@ def readImage(path): return b64img -def get_model(): +def get_model(use_sd15: bool = True) -> str: r = requests.get(BASE_URL+"/controlnet/model_list") result = r.json() - if "model_list" in result: - result = result["model_list"] - for item in result: - print("Using model: ", item) - return item - return "None" - + if "model_list" not in result: + return "None" + + def is_sd15(model_name: str) -> bool: + return 'sd15' in model_name + + candidates = [ + model + for model in result["model_list"] + if (use_sd15 and is_sd15(model)) or (not use_sd15 and not is_sd15(model)) + ] + + return candidates[0] if candidates else "None" + def get_modules(): return requests.get(f"{BASE_URL}/controlnet/module_list").json() diff --git a/tests/web_api/img2img_test.py b/tests/web_api/img2img_test.py index bb1df5611..0acdd3b12 100644 --- a/tests/web_api/img2img_test.py +++ b/tests/web_api/img2img_test.py @@ -75,13 +75,6 @@ def setup_route(self, setup_args): def assert_status_ok(self): self.assertEqual(requests.post(self.url_img2img, json=self.simple_img2img).status_code, 200) - stderr = "" - with open('test/stderr.txt') as f: - stderr = f.read().lower() - with open('test/stderr.txt', 'w') as f: - # clear stderr file so we can easily parse the next test - f.write("") - self.assertFalse('error' in stderr, "Errors in stderr: \n" + stderr) def test_img2img_simple_performed(self): self.assert_status_ok() diff --git a/tests/web_api/txt2img_test.py b/tests/web_api/txt2img_test.py index 0d985076b..2e6333d8b 100644 --- a/tests/web_api/txt2img_test.py +++ b/tests/web_api/txt2img_test.py @@ -68,13 +68,6 @@ def setup_controlnet_params(self, setup_args): def assert_status_ok(self, msg=None): self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200, msg) - stderr = "" - with open('test/stderr.txt') as f: - stderr = f.read().lower() - with open('test/stderr.txt', 'w') as f: - # clear stderr file so that we can easily parse the next test - f.write("") - self.assertFalse('error' in stderr, "Errors in stderr: \n" + stderr) def test_txt2img_simple_performed(self): self.assert_status_ok() From dec57929e68e8f0790d4b5cf55bf85e8e22d6ed8 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Sat, 4 Nov 2023 00:13:45 -0400 Subject: [PATCH 7/9] Fix batch hijack tests --- tests/cn_script/batch_hijack_test.py | 2 ++ tests/utils.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/cn_script/batch_hijack_test.py b/tests/cn_script/batch_hijack_test.py index 64e1894d6..7258302b9 100644 --- a/tests/cn_script/batch_hijack_test.py +++ b/tests/cn_script/batch_hijack_test.py @@ -42,6 +42,7 @@ def test_do_hijack__multiple_times__hijacks_once(self, process_images_inner_mock class TestGetControlNetBatchesWorks(unittest.TestCase): def setUp(self): self.p = unittest.mock.MagicMock() + assert scripts.scripts_txt2img is not None self.p.scripts = scripts.scripts_txt2img self.cn_script = controlnet.Script() self.p.scripts.alwayson_scripts = [self.cn_script] @@ -193,6 +194,7 @@ class TestProcessImagesPatchWorks(unittest.TestCase): def setUp(self, on_script_unloaded_mock): self.on_script_unloaded_mock = on_script_unloaded_mock self.p = unittest.mock.MagicMock() + assert scripts.scripts_txt2img is not None self.p.scripts = scripts.scripts_txt2img self.cn_script = controlnet.Script() self.p.scripts.alwayson_scripts = [self.cn_script] diff --git a/tests/utils.py b/tests/utils.py index 0e3013599..832b24eec 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -21,7 +21,9 @@ def setup_test_env(): sys.path.append(str(p)) # Initialize shared opts. - import webui + from modules import initialize + initialize.imports() + initialize.initialize() def readImage(path): From df5ee310a111c95016b249c7fd670b5f57c95e37 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Sat, 4 Nov 2023 00:26:06 -0400 Subject: [PATCH 8/9] change coverage dir --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d9dbd6a76..3055903af 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -66,7 +66,7 @@ jobs: - name: Run tests run: | wait-for-it --service 127.0.0.1:7860 -t 600 - python -m pytest -vv --junitxml=test/results.xml --cov . --cov-report=xml --verify-base-url ./extensions/sd-webui-controlnet/tests + python -m pytest -vv --junitxml=test/results.xml --cov ./extensions/sd-webui-controlnet --cov-report=xml --verify-base-url ./extensions/sd-webui-controlnet/tests working-directory: stable-diffusion-webui - name: Kill test server if: always() From 4582bb4c03842f7cb54733a7445e31e8d6b25b45 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Sat, 4 Nov 2023 00:47:07 -0400 Subject: [PATCH 9/9] :memo: Add doc --- tests/README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/README.md diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000..052e30a49 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,41 @@ +# Tests +There are 2 types of tests: +- unittest: backend based tests that directly import A1111 shared modules +- api test: test functionality through A1111 web API + +# Run tests locally +Make sure the current working directory is A1111 root. + +## Install test dependencies +`pip install -r requirements-test.txt` + +## Start test server +```shell +python -m coverage run + --data-file=.coverage.server + launch.py + --skip-prepare-environment + --skip-torch-cuda-test + --test-server + --do-not-download-clip + --no-half + --disable-opt-split-attention + --use-cpu all + --api-server-stop +``` + +## Run test +```shell +python -m pytest -vv --junitxml=test/results.xml --cov ./extensions/sd-webui-controlnet --cov-report=xml --verify-base-url ./extensions/sd-webui-controlnet/tests +``` + +## Check code coverage +Text report +```shell +python -m coverage report -i +``` + +HTML report +```shell +python -m coverage html -i +``` \ No newline at end of file