You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by jf...@apache.org on 2026/09/10 06:20:44 UTC
svn commit: r1938069 - in httpd/httpd/trunk: .github/workflows test test/modules/aaa test/pyhttpd
Author: jfclere
Date: Thu Sep 10 06:20:43 2026
New Revision: 1938069
Log:
Add windows tests to the CI
This closes #747
Modified:
httpd/httpd/trunk/.github/workflows/windows.yml
httpd/httpd/trunk/test/conftest.py
httpd/httpd/trunk/test/modules/aaa/test_009_restart.py
httpd/httpd/trunk/test/modules/aaa/test_010_eviction.py
httpd/httpd/trunk/test/pyhttpd/build-win.bat
httpd/httpd/trunk/test/pyhttpd/env.py
httpd/httpd/trunk/test/pyhttpd/install_tools.bat
Modified: httpd/httpd/trunk/.github/workflows/windows.yml
==============================================================================
--- httpd/httpd/trunk/.github/workflows/windows.yml Thu Sep 10 05:20:44 2026 (r1938068)
+++ httpd/httpd/trunk/.github/workflows/windows.yml Thu Sep 10 06:20:43 2026 (r1938069)
@@ -46,11 +46,11 @@ jobs:
- name: Default
triplet: x64-windows
arch: x64
- build-type: Debug
+ build-type: Release
generator: "Ninja"
runs-on: windows-latest
- timeout-minutes: 30
+ timeout-minutes: 60
name: ${{ matrix.name }}
env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
@@ -71,7 +71,7 @@ jobs:
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- name: Install dependencies
- run: vcpkg install --triplet ${{ matrix.triplet }} apr[private-headers] apr-util pcre2 openssl
+ run: vcpkg install --triplet ${{ matrix.triplet }} apr[private-headers] apr-util pcre2 openssl nghttp2 curl libxml2 jansson
- uses: actions/checkout@v6
@@ -82,8 +82,42 @@ jobs:
-G "${{ matrix.generator }}" `
-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake `
-DAPR_INCLUDE_DIR=C:/vcpkg/installed/${{ matrix.triplet }}/include `
- "-DAPR_LIBRARIES=C:/vcpkg/installed/${{ matrix.triplet }}/lib/libapr-1.lib;C:/vcpkg/installed/${{ matrix.triplet }}/lib/libaprutil-1.lib"
+ "-DAPR_LIBRARIES=C:/vcpkg/installed/${{ matrix.triplet }}/lib/libapr-1.lib;C:/vcpkg/installed/${{ matrix.triplet }}/lib/libaprutil-1.lib" `
+ -DNGHTTP2_INCLUDE_DIR=C:/vcpkg/installed/${{ matrix.triplet }}/include `
+ -DNGHTTP2_LIBRARIES=C:/vcpkg/installed/${{ matrix.triplet }}/lib/nghttp2.lib `
+ -DJANSSON_LIBRARIES=C:/vcpkg/installed/${{ matrix.triplet }}/lib/jansson.lib `
+ -DBUILD_TEST_MODULES=true
- name: Build
run: |
- cmake --build ${{github.workspace}}/build --config ${{ matrix.build-type }}
+ cmake --build ${{github.workspace}}/build --config ${{ matrix.build-type }} -- -j 1
+
+ - name: Install
+ run: |
+ cmake --install ${{github.workspace}}/build --config ${{ matrix.build-type }}
+
+ - name: Install dependencies
+ env:
+ VCPKG_DIR: C:\vcpkg
+ VCPKG_TRIPLET: ${{ matrix.triplet }}
+ run: |
+ ${{github.workspace}}\test\pyhttpd\install_tools.bat "C:\Program Files (x86)\HTTPD"
+
+ - name: Check httpd
+ run: |
+ $env:PATH = "C:\Program Files (x86)\HTTPD\bin;C:\vcpkg\installed\x64-windows\bin;$env:PATH"
+ & "C:\Program Files (x86)\HTTPD\bin\httpd.exe" -V
+ & "C:\Program Files (x86)\HTTPD\bin\httpd.exe" -t -f "C:\Program Files (x86)\HTTPD\conf\httpd.conf"
+
+ - name: Test httpd
+ run: |
+ ${{github.workspace}}\test\pyhttpd\runtests.bat --httpd "C:\Program Files (x86)\HTTPD\bin\httpd.exe" --conf "C:\Program Files (x86)\HTTPD\conf\httpd.conf" -vvvv -x
+
+ - name: Upload test logs
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-logs
+ path: |
+ C:\Program Files (x86)\HTTPD\logs\
+ ${{github.workspace}}\test\gen\apache\logs\
Modified: httpd/httpd/trunk/test/conftest.py
==============================================================================
--- httpd/httpd/trunk/test/conftest.py Thu Sep 10 05:20:44 2026 (r1938068)
+++ httpd/httpd/trunk/test/conftest.py Thu Sep 10 06:20:43 2026 (r1938069)
@@ -1,3 +1,4 @@
+import socket
import sys
import os
import re
@@ -11,6 +12,16 @@ sys.path.append(os.path.join(os.path.dir
from pyhttpd.env import HttpdTestEnv
+def _has_ipv6():
+ try:
+ s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+ s.bind(('::1', 0))
+ s.close()
+ return True
+ except (socket.error, OSError):
+ return False
+
+
def pytest_ignore_collect(collection_path, config):
if "pytest_suite" in collection_path.parts:
return True
@@ -177,6 +188,8 @@ def _package_scope(env, request):
"AH00338",
"AH00341",
])
+ if sys.platform == "win32" and _has_ipv6():
+ env.httpd_error_log.add_ignored_lognos(["AH00332"])
yield
assert env.apache_stop() == 0
env.check_error_log()
Modified: httpd/httpd/trunk/test/modules/aaa/test_009_restart.py
==============================================================================
--- httpd/httpd/trunk/test/modules/aaa/test_009_restart.py Thu Sep 10 05:20:44 2026 (r1938068)
+++ httpd/httpd/trunk/test/modules/aaa/test_009_restart.py Thu Sep 10 06:20:43 2026 (r1938069)
@@ -39,10 +39,6 @@ import sys
import pytest
-import sys
-
-import pytest
-
from . import digest_client as dc
from .env import AAATestEnv
@@ -152,6 +148,9 @@ class TestDigestRestart:
"a returning client was reported as a possible replay attack " \
"because its id had been given to somebody else"
+ @pytest.mark.xfail(sys.platform == "win32", reason=
+ "mpm_winnt child is a separate process, "
+ "ap_retained_data does not survive restart")
def test_digest_092_onetime_nonce_from_before_a_restart_is_not_accepted(self, env):
# One-time nonces are ordered by a counter which restarts at 0 with
# the segment, while the nonce itself stays verifiable. A client
@@ -187,6 +186,9 @@ class TestDigestRestart:
self.header(location, newcomer)).response["status"] == 200, \
"the stale nonce locked out the client holding that id"
+ @pytest.mark.xfail(sys.platform == "win32", reason=
+ "mpm_winnt child is a separate process, "
+ "ap_retained_data does not survive restart")
def test_digest_093_onetime_request_cannot_be_replayed_across_a_restart(self, env):
# The severity case. A one-time nonce is single-use because the server
# remembers the last nonce each client used -- and that memory does
Modified: httpd/httpd/trunk/test/modules/aaa/test_010_eviction.py
==============================================================================
--- httpd/httpd/trunk/test/modules/aaa/test_010_eviction.py Thu Sep 10 05:20:44 2026 (r1938068)
+++ httpd/httpd/trunk/test/modules/aaa/test_010_eviction.py Thu Sep 10 06:20:43 2026 (r1938069)
@@ -25,13 +25,17 @@ a lifetime and a counter which starts at
ever 0. The tests below run against both to pin that.
"""
+import sys
+
import pytest
from . import digest_client as dc
from .env import AAATestEnv
NC_FAILED = "AH01774"
+NONCE_INVALID = "AH01776"
CLIENT_UNKNOWN = "AH10618"
+_WIN32 = sys.platform == "win32"
# A location tracking clients for the nonce-count, and one tracking them for
# one-time nonces: the two put different kinds of value in last_nonce_time.
@@ -78,6 +82,9 @@ class TestDigestEviction:
for _ in range(count):
env.curl_get(self.url(env, location))
+ @pytest.mark.xfail(sys.platform == "win32", reason=
+ "mpm_winnt child is a separate process, "
+ "ap_retained_data does not survive restart")
@pytest.mark.parametrize("location", BOTH)
def test_digest_100_authenticated_client_survives_a_flood(self, env, location):
# The legitimate client authenticates, so its entry now records the
@@ -94,7 +101,8 @@ class TestDigestEviction:
# The victim carries on. Its entry must still be there: it is the
# only one in the table which is worth keeping.
r = self.send(env, location, self.header(location, challenge, nc))
- env.httpd_error_log.ignore_recent(lognos=[NC_FAILED, CLIENT_UNKNOWN])
+ env.httpd_error_log.ignore_recent(lognos=[NC_FAILED, CLIENT_UNKNOWN]
+ + ([NONCE_INVALID] if _WIN32 else []))
assert r.response["status"] == 200, \
"a flood of unauthenticated requests evicted an authenticated client"
@@ -110,7 +118,8 @@ class TestDigestEviction:
self.flood(env, location, 60)
r = self.send(env, location, self.header(location, first))
- env.httpd_error_log.ignore_recent(lognos=[NC_FAILED, CLIENT_UNKNOWN])
+ env.httpd_error_log.ignore_recent(lognos=[NC_FAILED, CLIENT_UNKNOWN]
+ + ([NONCE_INVALID] if _WIN32 else []))
assert r.response["status"] == 401
again = dc.DigestChallenge.parse(r.response["header"]["www-authenticate"])
assert again.opaque != first.opaque, \
Modified: httpd/httpd/trunk/test/pyhttpd/build-win.bat
==============================================================================
--- httpd/httpd/trunk/test/pyhttpd/build-win.bat Thu Sep 10 05:20:44 2026 (r1938068)
+++ httpd/httpd/trunk/test/pyhttpd/build-win.bat Thu Sep 10 06:20:43 2026 (r1938069)
@@ -107,9 +107,10 @@ cd /d "%HTTPD_SRC%\test\pyhttpd"
SET CWD=%CD%
SET "CWD=%CWD:\=/%"
@REM -------------------------------------
-SET ARCH="x64-windows"
-SET BUILD_TYPE="Debug"
-SET VCPKG_DIRECTORY="vcpkg\installed\x64-windows"
+SET BUILD_TYPE="Release"
+SET VCPKG_ROOT=%CWD%\vcpkg
+SET VCPKG_TRIPLET=x64-windows-release
+SET VCPKG_DIRECTORY="vcpkg\installed\%VCPKG_TRIPLET%"
SET VCPKG_DIRECTORY_LIB=%VCPKG_DIRECTORY%
SET GENERATOR="NMake Makefiles"
@REM SET GENERATOR="Ninja"
@@ -230,9 +231,8 @@ if NOT EXIST vcpkg\ (
PUSHD vcpkg\
)
-SET VCPKG_ROOT=%CWD%\vcpkg
@REM For some reason using the manifest doesn't install the default-features
-vcpkg.exe install --triplet x64-windows apr[private-headers] apr-util pcre2 openssl nghttp2 curl libxml2 jansson
+vcpkg.exe install --triplet %VCPKG_TRIPLET% apr[private-headers] apr-util pcre2 openssl nghttp2 curl libxml2 jansson
POPD
@@ -256,13 +256,13 @@ cmake "%HTTPD_SRC%" -B . ^
-G %GENERATOR% ^
-DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^
-DCMAKE_TOOLCHAIN_FILE=%CWD%/vcpkg/scripts/buildsystems/vcpkg.cmake ^
- -DVCPKG_TARGET_TRIPLET=%ARCH% ^
- -DNGHTTP2_INCLUDE_DIR=%CWD%/vcpkg/installed/x64-windows/include ^
- -DAPR_INCLUDE_DIR=%CWD%/vcpkg/installed/x64-windows/include ^
- -DJANSSON_INCLUDE_DIR=%CWD%/vcpkg/installed/x64-windows/include ^
- "-DAPR_LIBRARIES=%CWD%/vcpkg/installed/x64-windows/lib/libapr-1.lib;%CWD%/vcpkg/installed/x64-windows/lib/libaprutil-1.lib" ^
- -DNGHTTP2_LIBRARIES=%CWD%/vcpkg/installed/x64-windows/lib/nghttp2.lib ^
- -DJANSSON_LIBRARIES=%CWD%/vcpkg/installed/x64-windows/lib/jansson.lib ^
+ -DVCPKG_TARGET_TRIPLET=%VCPKG_TRIPLET% ^
+ -DNGHTTP2_INCLUDE_DIR=%CWD%/vcpkg/installed/%VCPKG_TRIPLET%/include ^
+ -DAPR_INCLUDE_DIR=%CWD%/vcpkg/installed/%VCPKG_TRIPLET%/include ^
+ -DJANSSON_INCLUDE_DIR=%CWD%/vcpkg/installed/%VCPKG_TRIPLET%/include ^
+ "-DAPR_LIBRARIES=%CWD%/vcpkg/installed/%VCPKG_TRIPLET%/lib/libapr-1.lib;%CWD%/vcpkg/installed/%VCPKG_TRIPLET%/lib/libaprutil-1.lib" ^
+ -DNGHTTP2_LIBRARIES=%CWD%/vcpkg/installed/%VCPKG_TRIPLET%/lib/nghttp2.lib ^
+ -DJANSSON_LIBRARIES=%CWD%/vcpkg/installed/%VCPKG_TRIPLET%/lib/jansson.lib ^
-DBUILD_TEST_MODULES=true ^
-DCMAKE_POLICY_VERSION_MINIMUM=%CMAKE_VERSION% ^
--install-prefix %HTTPD_INSTALL_DIRECTORY%
Modified: httpd/httpd/trunk/test/pyhttpd/env.py
==============================================================================
--- httpd/httpd/trunk/test/pyhttpd/env.py Thu Sep 10 05:20:44 2026 (r1938068)
+++ httpd/httpd/trunk/test/pyhttpd/env.py Thu Sep 10 06:20:43 2026 (r1938069)
@@ -907,10 +907,18 @@ class HttpdTestEnv:
def _win_stop(self) -> int:
if self._httpd_proc is not None:
- log.debug("stopping httpd (terminate parent)")
- self._httpd_proc.terminate()
+ import ctypes
+ log.debug("stopping httpd (signal shutdown event)")
+ event_name = f"ap{self._httpd_proc.pid}_shutdown"
+ handle = ctypes.windll.kernel32.OpenEventW(0x0002, False, event_name)
+ if handle:
+ ctypes.windll.kernel32.SetEvent(handle)
+ ctypes.windll.kernel32.CloseHandle(handle)
+ else:
+ log.warning(f"cannot open {event_name}, falling back to terminate")
+ self._httpd_proc.terminate()
try:
- self._httpd_proc.wait(timeout=10)
+ self._httpd_proc.wait(timeout=30)
except subprocess.TimeoutExpired:
self._httpd_proc.kill()
self._httpd_proc.wait(timeout=5)
Modified: httpd/httpd/trunk/test/pyhttpd/install_tools.bat
==============================================================================
--- httpd/httpd/trunk/test/pyhttpd/install_tools.bat Thu Sep 10 05:20:44 2026 (r1938068)
+++ httpd/httpd/trunk/test/pyhttpd/install_tools.bat Thu Sep 10 06:20:43 2026 (r1938069)
@@ -169,9 +169,34 @@ echo install_tools.bat: Strawberry Perl
:perl_ok
rem --- vcpkg location ---------------------------------------------------------
+if not defined VCPKG_TRIPLET set "VCPKG_TRIPLET=x64-windows-release"
+
+rem Honor VCPKG_DIR from the environment (e.g. set by CI)
+if defined VCPKG_DIR (
+ if not defined VCPKG_INSTALLED set "VCPKG_INSTALLED=%VCPKG_DIR%\installed\%VCPKG_TRIPLET%"
+ if exist "%VCPKG_DIR%\vcpkg.exe" (
+ set "VCPKG_EXE=%VCPKG_DIR%\vcpkg.exe"
+ echo install_tools.bat: using VCPKG_DIR from environment: "!VCPKG_DIR!" >&2
+ goto :vcpkg_ok
+ )
+)
+
+rem Try vcpkg on PATH (e.g. GitHub Actions has C:\vcpkg)
+set "VCPKG_EXE="
+for /f "delims=" %%V in ('where vcpkg.exe 2^>nul') do (
+ if not defined VCPKG_EXE set "VCPKG_EXE=%%V"
+)
+if defined VCPKG_EXE (
+ echo install_tools.bat: using vcpkg from PATH: "!VCPKG_EXE!" >&2
+ for %%F in ("!VCPKG_EXE!") do set "VCPKG_DIR=%%~dpF"
+ if "!VCPKG_DIR:~-1!"=="\" set "VCPKG_DIR=!VCPKG_DIR:~0,-1!"
+ if not defined VCPKG_INSTALLED set "VCPKG_INSTALLED=!VCPKG_DIR!\installed\%VCPKG_TRIPLET%"
+ goto :vcpkg_ok
+)
+
+rem Fall back to cloning vcpkg locally
set "VCPKG_DIR=%TEST_DIR%\vcpkg"
-set "VCPKG_TRIPLET=x64-windows"
-set "VCPKG_INSTALLED=%VCPKG_DIR%\installed\%VCPKG_TRIPLET%"
+if not defined VCPKG_INSTALLED set "VCPKG_INSTALLED=%VCPKG_DIR%\installed\%VCPKG_TRIPLET%"
rem --- bootstrap vcpkg if needed ----------------------------------------------
if not exist "%VCPKG_DIR%\" (
@@ -204,6 +229,8 @@ if not exist "%VCPKG_DIR%\vcpkg.exe" (
icacls "%VCPKG_DIR%\vcpkg.exe" /grant "%USERNAME%":RX >nul 2>&1
set "VCPKG_EXE=%VCPKG_DIR%\vcpkg.exe"
+:vcpkg_ok
+
rem --- install packages -------------------------------------------------------
echo install_tools.bat: installing curl[core,tool,http2,openssl] and nghttp2 via vcpkg... >&2
"%VCPKG_EXE%" install --recurse --triplet %VCPKG_TRIPLET% curl[core,tool,http2,openssl] nghttp2