From bb4ab2924a59762c58e9c89a85f0b206f4d06188 Mon Sep 17 00:00:00 2001 From: Alexandre Rossi Date: Mon, 29 Apr 2024 22:53:52 +0200 Subject: [PATCH 1/4] fix uwsgi_regexp_match() with pcre2 (Fix #2634) pcre2_match() with no match_data structure does not work --- core/regexp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/regexp.c b/core/regexp.c index 74bb77751..3c3e15429 100644 --- a/core/regexp.c +++ b/core/regexp.c @@ -69,7 +69,7 @@ int uwsgi_regexp_build(char *re, uwsgi_pcre ** pattern) { int uwsgi_regexp_match(uwsgi_pcre *pattern, const char *subject, int length) { #ifdef UWSGI_PCRE2 - return pcre2_match(pattern, (const unsigned char *)subject, length, 0, 0, NULL, NULL); + return uwsgi_regexp_match_ovec(pattern, subject, length, NULL, 0); #else return pcre_exec((const pcre *) pattern->p, (const pcre_extra *) pattern->extra, subject, length, 0, 0, NULL, 0); #endif From f11e103e707023896047683f1b214e87f374091a Mon Sep 17 00:00:00 2001 From: Alexandre Rossi Date: Mon, 29 Apr 2024 23:08:01 +0200 Subject: [PATCH 2/4] add unittest for uwsgi_regexp_match() --- Makefile | 3 +- {check => unittest}/Makefile | 5 +- {check => unittest}/check_core.c | 0 unittest/check_regexp.c | 86 ++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 3 deletions(-) rename {check => unittest}/Makefile (84%) rename {check => unittest}/check_core.c (100%) create mode 100644 unittest/check_regexp.c diff --git a/Makefile b/Makefile index 430c7ce80..b576335a3 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ all: clean: $(PYTHON) uwsgiconfig.py --clean + cd unittest && make clean check: $(PYTHON) uwsgiconfig.py --check @@ -14,7 +15,7 @@ plugin.%: tests: $(PYTHON) uwsgiconfig.py --build unittest - cd check && make && make test + cd unittest && make && make test %: $(PYTHON) uwsgiconfig.py --build $@ diff --git a/check/Makefile b/unittest/Makefile similarity index 84% rename from check/Makefile rename to unittest/Makefile index a9a655576..f0c22acef 100644 --- a/check/Makefile +++ b/unittest/Makefile @@ -1,5 +1,6 @@ CFLAGS = $(shell pkg-config --cflags check) +CFLAGS += -DUWSGI_PCRE2 LDFLAGS = $(shell pkg-config --libs check) LDFLAGS += -ldl -lz LDFLAGS += $(shell xml2-config --libs) @@ -13,11 +14,11 @@ ifeq ($(UNAME_S),Linux) endif -objects = check_core +objects = check_core check_regexp all: $(objects) -$(objects): %: %.c +$(objects): %: %.c ../libuwsgi.a $(CC) $(CFLAGS) -o $@ $< ../libuwsgi.a $(LDFLAGS) test: diff --git a/check/check_core.c b/unittest/check_core.c similarity index 100% rename from check/check_core.c rename to unittest/check_core.c diff --git a/unittest/check_regexp.c b/unittest/check_regexp.c new file mode 100644 index 000000000..b2025bfb2 --- /dev/null +++ b/unittest/check_regexp.c @@ -0,0 +1,86 @@ +#include +#include "../uwsgi.h" + + +START_TEST(test_uwsgi_regexp_match) +{ + int result; + uwsgi_pcre *pattern_all; + uwsgi_pcre *pattern; + + result = uwsgi_regexp_build(".*", &pattern_all); + ck_assert(result == 0); + + result = uwsgi_regexp_match(pattern_all, "/fooba", 6); + ck_assert(result >= 0); + + result = uwsgi_regexp_build("/foobar/.*", &pattern); + ck_assert(result == 0); + + result = uwsgi_regexp_match(pattern, "/fooba", 6); + ck_assert(result < 0); + + result = uwsgi_regexp_match(pattern, "/foobar/baz", 11); + ck_assert(result >= 0); + + pcre2_code_free(pattern_all); + pcre2_code_free(pattern); +} +END_TEST + +START_TEST(test_uwsgi_regexp_match_ovec) +{ + int result; + uwsgi_pcre *pattern; + int *ovec = calloc((2+1)*2, sizeof(int)); + char buf[20], sub[20]; + + result = uwsgi_regexp_build("^/foo/(.*)\\.jpg\\?([0-9]{2})", &pattern); + ck_assert(result == 0); + result = uwsgi_regexp_ovector(pattern); + ck_assert(result == 2); + + result = uwsgi_regexp_match_ovec(pattern, "/fooba", 6, ovec, 2); + ck_assert(result < 0); + + strcpy(buf, "/foo/bar.jpg?422"); + result = uwsgi_regexp_match_ovec(pattern, buf, strlen(buf), ovec, 2); + ck_assert(result >= 0); + strncpy(sub, buf+ovec[0], ovec[1]-ovec[0]); + sub[ovec[1]-ovec[0]] = '\0'; + ck_assert_str_eq(sub, "/foo/bar.jpg?42"); + strncpy(sub, buf+ovec[2], ovec[3]-ovec[2]); + sub[ovec[3]-ovec[2]] = '\0'; + ck_assert_str_eq(sub, "bar"); + strncpy(sub, buf+ovec[4], ovec[5]-ovec[4]); + sub[ovec[5]-ovec[4]] = '\0'; + ck_assert_str_eq(sub, "42"); + + strcpy(sub, uwsgi_regexp_apply_ovec(buf, strlen(buf), "key=$1.$2.jpg", 13, ovec, 2)); + ck_assert_str_eq(sub, "key=bar.42.jpg"); + + pcre2_code_free(pattern); + free(ovec); +} +END_TEST + +Suite *check_regexp(void) +{ + Suite *s = suite_create("uwsgi regexp"); + TCase *tc = tcase_create("regexp"); + + suite_add_tcase(s, tc); + tcase_add_test(tc, test_uwsgi_regexp_match); + tcase_add_test(tc, test_uwsgi_regexp_match_ovec); + return s; +} + +int main(void) +{ + int nf; + SRunner *r = srunner_create(check_regexp()); + srunner_run_all(r, CK_NORMAL); + nf = srunner_ntests_failed(r); + srunner_free(r); + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} From 5c6a3dcb556f175df796b26c6091fadfc2f2fdeb Mon Sep 17 00:00:00 2001 From: Alexandre Rossi Date: Mon, 29 Apr 2024 23:09:39 +0200 Subject: [PATCH 3/4] make unittest a specific test job also rename unittests as such, making the distinction with tests and checks. --- .github/workflows/test.yml | 32 ++++++++++++++++++-------------- Makefile | 4 ++-- unittest/Makefile | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c396344d..19f06baa0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,13 +7,26 @@ on: branches: [ master, uwsgi-2.0 ] jobs: - python: + unittest: + runs-on: ubuntu-20.04 + steps: + - name: Install dependencies + run: | + sudo apt update -qq + sudo apt install --no-install-recommends -qqyf \ + libpcre2-dev libjansson-dev libcap2-dev \ + check + - uses: actions/checkout@v4 + - name: Run unit tests + run: make unittests + + python: runs-on: ubuntu-20.04 strategy: matrix: python-version: ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] - test-suite: [unittest, python, deadlocks] + test-suite: [python, deadlocks] steps: - name: Add deadnakes ppa run: sudo add-apt-repository ppa:deadsnakes/ppa -y @@ -22,34 +35,27 @@ jobs: sudo apt update -qq sudo apt install --no-install-recommends -qqyf python${{ matrix.python-version }}-dev \ libpcre2-dev libjansson-dev libcap2-dev \ - curl check + curl - name: Install distutils if: contains(fromJson('["3.6","3.7","3.8","3.9","3.10","3.11","3.12"]'), matrix.python-version) run: | sudo apt install --no-install-recommends -qqyf python${{ matrix.python-version }}-distutils \ - uses: actions/checkout@v4 - - name: Run unit tests - if: matrix.test-suite == 'unittest' - run: make tests - name: Build uWSGI binary - if: matrix.test-suite != 'unittest' run: make - name: Build python${{ matrix.python-version }} plugin - if: matrix.test-suite != 'unittest' run: | PYTHON_VERSION=${{ matrix.python-version }} PYTHON_VERSION=python${PYTHON_VERSION//.} /usr/bin/python${{ matrix.python-version }} -V /usr/bin/python${{ matrix.python-version }} uwsgiconfig.py --plugin plugins/python base $PYTHON_VERSION - name: run smoke tests - if: matrix.test-suite != 'unittest' run: | PYTHON_VERSION=${{ matrix.python-version }} PYTHON_VERSION=python${PYTHON_VERSION//.} ./tests/gh-${{ matrix.test-suite }}.sh ${PYTHON_VERSION} rack: - runs-on: ubuntu-20.04 strategy: matrix: @@ -59,11 +65,9 @@ jobs: run: | sudo apt update -qq sudo apt install --no-install-recommends -qqyf python3-dev \ - libpcre3-dev libjansson-dev libcap2-dev ruby2.7-dev \ - curl check + libpcre2-dev libjansson-dev libcap2-dev ruby2.7-dev \ + curl - uses: actions/checkout@v4 - - name: Run unit tests - run: make tests - name: Build uWSGI binary run: make - name: Build rack plugin diff --git a/Makefile b/Makefile index b576335a3..2a4f0d678 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,9 @@ check: plugin.%: $(PYTHON) uwsgiconfig.py --plugin plugins/$* $(PROFILE) -tests: +unittests: $(PYTHON) uwsgiconfig.py --build unittest - cd unittest && make && make test + cd unittest && make test %: $(PYTHON) uwsgiconfig.py --build $@ diff --git a/unittest/Makefile b/unittest/Makefile index f0c22acef..c28b8d70c 100644 --- a/unittest/Makefile +++ b/unittest/Makefile @@ -21,7 +21,7 @@ all: $(objects) $(objects): %: %.c ../libuwsgi.a $(CC) $(CFLAGS) -o $@ $< ../libuwsgi.a $(LDFLAGS) -test: +test: all @for file in $(objects); do ./$$file; done clean: From 1a71973117bd6490d2d48e7c2fcd34cf9aa3f7fb Mon Sep 17 00:00:00 2001 From: Alexandre Rossi Date: Tue, 30 Apr 2024 11:23:42 +0200 Subject: [PATCH 4/4] add integration test for static-expires-uri --- .github/workflows/test.yml | 12 ++++++ Makefile | 3 ++ t/runner | 82 ++++++++++++++++++++++++++++++++++++++ t/static/config.ini | 4 ++ 4 files changed, 101 insertions(+) create mode 100755 t/runner create mode 100644 t/static/config.ini diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 19f06baa0..88df146b6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,18 @@ jobs: - name: Run unit tests run: make unittests + test: + runs-on: ubuntu-20.04 + steps: + - name: Install dependencies + run: | + sudo apt update -qq + sudo apt install --no-install-recommends -qqyf \ + libpcre2-dev libjansson-dev libcap2-dev + - uses: actions/checkout@v4 + - name: Run integration tests + run: make all tests + python: runs-on: ubuntu-20.04 strategy: diff --git a/Makefile b/Makefile index 2a4f0d678..fd2e3301c 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ unittests: $(PYTHON) uwsgiconfig.py --build unittest cd unittest && make test +tests: + $(PYTHON) t/runner + %: $(PYTHON) uwsgiconfig.py --build $@ diff --git a/t/runner b/t/runner new file mode 100755 index 000000000..b9d9ce529 --- /dev/null +++ b/t/runner @@ -0,0 +1,82 @@ +#!/usr/bin/python3 + + +import os +import requests +import signal +import socket +import subprocess +import time +import unittest + + +TESTS_DIR = os.path.dirname(__file__) +UWSGI_BINARY = os.getenv("UWSGI_BINARY", os.path.join(TESTS_DIR, "..", "uwsgi")) +UWSGI_ADDR = "127.0.0.1" +UWSGI_PORT = 8000 +UWSGI_HTTP = f"{UWSGI_ADDR}:{UWSGI_PORT}" + + +class BaseTest: + """ + Container class to avoid base test being run + """ + + class UwsgiServerTest(unittest.TestCase): + """ + Test case with a server instance available on a socket for requests + """ + + @classmethod + def uwsgi_ready(cls): + try: + s = socket.socket() + s.connect( + ( + UWSGI_ADDR, + UWSGI_PORT, + ) + ) + except socket.error: + return False + else: + return True + finally: + s.close() + + @classmethod + def setUpClass(cls): + # launch server + cls.testserver = subprocess.Popen( + [UWSGI_BINARY, "--http-socket", UWSGI_HTTP] + cls.ARGS + ) + + # ensure server is ready + retries = 10 + while not cls.uwsgi_ready() and retries > 0: + time.sleep(0.1) + retries = retries - 1 + if retries == 0: + raise RuntimeError("uwsgi test server is not available") + + @classmethod + def tearDownClass(cls): + cls.testserver.send_signal(signal.SIGTERM) + cls.testserver.wait() + + +class StaticTest(BaseTest.UwsgiServerTest): + + ARGS = [ + "--plugin", + "python3", # provide a request plugin if no embedded request plugin + os.path.join(TESTS_DIR, "static", "config.ini"), + ] + + def test_static_expires(self): + with requests.get(f"http://{UWSGI_HTTP}/foobar/config.ini") as r: + self.assertTrue("Expires" in r.headers) + + +if __name__ == "__main__": + unittest.main() diff --git a/t/static/config.ini b/t/static/config.ini new file mode 100644 index 000000000..e0e23c55e --- /dev/null +++ b/t/static/config.ini @@ -0,0 +1,4 @@ +[uwsgi] +need-app = False +static-map = /foobar=t/static +static-expires-uri = ^/foobar/ 315360000