diff --git a/ci/build_test_OnCommit.groovy b/ci/build_test_OnCommit.groovy index e68487d636..eaabf7fc02 100644 --- a/ci/build_test_OnCommit.groovy +++ b/ci/build_test_OnCommit.groovy @@ -210,7 +210,6 @@ pipeline { windows.setup_bazel_remote_cache() windows.install_dependencies() windows.unit_test() - windows.check_tests() } finally { windows.archive_test_artifacts() } diff --git a/ci/build_test_OnCommitWin.groovy b/ci/build_test_OnCommitWin.groovy index 2ac7e697d3..337088d024 100644 --- a/ci/build_test_OnCommitWin.groovy +++ b/ci/build_test_OnCommitWin.groovy @@ -20,7 +20,6 @@ pipeline { windows.clean() windows.build() windows.unit_test() - windows.check_tests() if (env.USE_BRANCH_NAME_IN_PACKAGE_NAME == "true") { def safeBranchName = env.BRANCH_NAME.replaceAll('/', '_') tag = "${safeBranchName}-${env.CUSTOM_TAG}" diff --git a/ci/build_test_release.groovy b/ci/build_test_release.groovy index 4f88ee17fb..bee5438fbf 100644 --- a/ci/build_test_release.groovy +++ b/ci/build_test_release.groovy @@ -24,7 +24,6 @@ pipeline { windows.clean() windows.build() windows.unit_test() - windows.check_tests() def safeBranchName = env.BRANCH_NAME.replaceAll('/', '_') def python_presence = "" if (env.OVMS_PYTHON_ENABLED == "1") { diff --git a/ci/loadWin.groovy b/ci/loadWin.groovy index d2860b6fbc..983bbc03df 100644 --- a/ci/loadWin.groovy +++ b/ci/loadWin.groovy @@ -4,23 +4,23 @@ def cleanup_directories() { // First delete directories older than 14 days deleteOldDirectories() def command = 'ls c:\\Jenkins\\workspace | grep -oE "(PR-[0-9]*)$"' - def status = bat(returnStatus: true, script: command) + def status = bat(returnStatus: true, script: '@' + command) if ( status != 0) { error "Error: trying to list jenkins workspaces." } - def existing_workspace_string = bat(returnStatus: false, returnStdout: true, script: command) + def existing_workspace_string = bat(returnStatus: false, returnStdout: true, script: '@' + command) println existing_workspace_string def existing_workspace = existing_workspace_string.split(/\n/) command = 'ls c:\\ | grep -oE "(PR-[0-9]*)$"' - status = bat(returnStatus: true, script: command) + status = bat(returnStatus: true, script: '@' + command) if ( status != 0) { println "No PR-XXXX detected for cleanup." return } - def existing_prs_string = bat(returnStatus: false, returnStdout: true, script: command) + def existing_prs_string = bat(returnStatus: false, returnStdout: true, script: '@' + command) println existing_prs_string def existing_prs = existing_prs_string.split(/\n/) @@ -46,7 +46,7 @@ def cleanup_directories() { error "Error: trying to delete a directory that is not expected: " + pathToDelete } else { println "Deleting: " + pathToDelete - status = bat(returnStatus: true, script: 'rmdir /s /q ' + pathToDelete) + status = bat(returnStatus: true, script: '@rmdir /s /q ' + pathToDelete) if (status != 0) { error "Error: Deleting directory ${pathToDelete} failed: ${status}. Check pipeline.log for details." } else { @@ -66,14 +66,14 @@ def get_short_bazel_path() { def deleteOldDirectories() { command = 'forfiles /P c:\\ /D -14 | grep -oE "(PR-[0-9]*)"' - status = bat(returnStatus: true, script: command) + status = bat(returnStatus: true, script: '@' + command) if ( status != 0) { println "No PR-XXXX older than 14 days for cleanup." return } // Check if directory was created more than 14 days ago - def existing_prs_string = bat(returnStatus: false, returnStdout: true, script: command) + def existing_prs_string = bat(returnStatus: false, returnStdout: true, script: '@' + command) println existing_prs_string @@ -88,7 +88,7 @@ def deleteOldDirectories() { error "Error: trying to delete a directory that is not expected: " + pathToDelete } else { println "Deleting: " + pathToDelete - status = bat(returnStatus: true, script: 'rmdir /s /q ' + pathToDelete) + status = bat(returnStatus: true, script: '@rmdir /s /q ' + pathToDelete) if (status != 0) { error "Error: Deleting directory ${pathToDelete} failed: ${status}. Check pipeline.log for details." } else { @@ -100,7 +100,7 @@ def deleteOldDirectories() { def install_dependencies() { println "Install dependencies on node: NODE_NAME = ${env.NODE_NAME}" - def status = bat(returnStatus: true, script: 'windows_install_build_dependencies.bat ' + get_short_bazel_path() + ' ' + env.OVMS_CLEAN_EXPUNGE) + def status = bat(returnStatus: true, script: '@windows_install_build_dependencies.bat ' + get_short_bazel_path() + ' ' + env.OVMS_CLEAN_EXPUNGE) if (status != 0) { error "Error: Windows install dependencies failed: ${status}. Check pipeline.log for details." } else { @@ -109,9 +109,9 @@ def install_dependencies() { } def clean() { - def output1 = bat(returnStdout: true, script: 'windows_clean_build.bat ' + get_short_bazel_path() + ' ' + env.OVMS_CLEAN_EXPUNGE) + def output1 = bat(returnStdout: true, script: '@windows_clean_build.bat ' + get_short_bazel_path() + ' ' + env.OVMS_CLEAN_EXPUNGE) if(fileExists('dist\\windows\\ovms')){ - def status_del = bat(returnStatus: true, script: 'rmdir /s /q dist\\windows\\ovms') + def status_del = bat(returnStatus: true, script: '@rmdir /s /q dist\\windows\\ovms') if (status_del != 0) { error "Error: Deleting existing ovms directory failed ${status_del}. Check pipeline.log for details." } else { @@ -123,21 +123,21 @@ def clean() { def build(){ println "OVMS_PYTHON_ENABLED=${env.OVMS_PYTHON_ENABLED}" def pythonOption = env.OVMS_PYTHON_ENABLED == "0" ? "--no_python" : "--with_python" - def status = bat(returnStatus: true, script: 'windows_build.bat ' + get_short_bazel_path() + ' ' + pythonOption + ' --with_tests') - status = bat(returnStatus: true, script: 'grep "Build completed successfully" win_build.log"') + def status = bat(returnStatus: true, script: '@windows_build.bat ' + get_short_bazel_path() + ' ' + pythonOption + ' --with_tests') + status = bat(returnStatus: true, script: '@grep "Build completed successfully" win_build.log"') if (status != 0) { error "Error: Windows build failed ${status}. Check win_build.log for details." } else { echo "Build successful." } - def status_pkg = bat(returnStatus: true, script: 'windows_create_package.bat ' + get_short_bazel_path() + ' ' + pythonOption) + def status_pkg = bat(returnStatus: true, script: '@windows_create_package.bat ' + get_short_bazel_path() + ' ' + pythonOption) if (status_pkg != 0) { error "Error: Windows package failed ${status_pkg}." } else { echo "Windows package created successfully." } def unzipCmd = "tar -xf dist\\windows\\ovms.zip" - def status_unzip = bat(returnStatus: true, script: "${unzipCmd}") + def status_unzip = bat(returnStatus: true, script: '@' + "${unzipCmd}") if (status_unzip != 0) { error "Error: Unzipping package failed: ${status_unzip}." } else { @@ -149,7 +149,7 @@ def clone_sdl_repo() { if(!fileExists('sdl_repo')){ println "Starting code signing" - def statusPull = bat(returnStatus: true, script: 'git clone -b ' + env.SIGN_REPO_BRANCH + ' ' + env.SIGN_REPO + ' sdl_repo') + def statusPull = bat(returnStatus: true, script: '@git clone -b ' + env.SIGN_REPO_BRANCH + ' ' + env.SIGN_REPO + ' sdl_repo') if (statusPull != 0) { error "Error: Downloading sdl_repo failed ${statusPull}. Check pipeline.log for details." } else { @@ -158,7 +158,7 @@ def clone_sdl_repo() }else{ println "Pulling latest changes in sdl_repo" dir('sdl_repo') { - def statusPull = bat(returnStatus: true, script: 'git fetch && git reset --hard origin/'+env.SIGN_REPO_BRANCH) + def statusPull = bat(returnStatus: true, script: '@git fetch && git reset --hard origin/'+env.SIGN_REPO_BRANCH) if (statusPull != 0) { error "Error: Pulling latest changes in sdl_repo failed ${statusPull}. Check pipeline.log for details." } else { @@ -172,7 +172,7 @@ def clone_bdba_repo() { if(!fileExists('repo_ci_infra')){ println "Starting BDBA infrastructure download" - def statusPull = bat(returnStatus: true, script: 'git clone -b ' + env.BDBA_REPO_BRANCH + ' ' + env.BDBA_REPO + ' repo_ci_infra') + def statusPull = bat(returnStatus: true, script: '@git clone -b ' + env.BDBA_REPO_BRANCH + ' ' + env.BDBA_REPO + ' repo_ci_infra') if (statusPull != 0) { error "Error: Downloading BDBA infrastructure failed ${statusPull}. Check pipeline.log for details." } else { @@ -181,7 +181,7 @@ def clone_bdba_repo() }else{ println "Pulling latest changes in BDBA infrastructure" dir('repo_ci_infra') { - def statusPull = bat(returnStatus: true, script: 'git fetch && git reset --hard origin/'+env.BDBA_REPO_BRANCH) + def statusPull = bat(returnStatus: true, script: '@git fetch && git reset --hard origin/'+env.BDBA_REPO_BRANCH) if (statusPull != 0) { error "Error: Pulling latest changes in BDBA infrastructure failed ${statusPull}. Check pipeline.log for details." } else { @@ -193,7 +193,7 @@ def clone_bdba_repo() def sign(){ println "SIGNING_USER=${env.SIGNING_USER}" - def status = bat(returnStatus: true, script: 'ci\\windows_sign.bat ' + env.SIGNING_USER + ' dist\\windows ' + env.OVMS_PYTHON_ENABLED) + def status = bat(returnStatus: true, script: '@ci\\windows_sign.bat ' + env.SIGNING_USER + ' dist\\windows ' + env.OVMS_PYTHON_ENABLED) if (status != 0) { error "Error: Windows code signing failed ${status}. Check win_sign.log for details." } else { @@ -203,7 +203,7 @@ def sign(){ def bdba(){ println "Starting BDBA scan" - def status = bat(returnStatus: true, script: 'ci\\windows_bdba.bat ' + env.BDBA_CREDS_PSW + ' dist\\windows sdl_repo\\ovms-package') + def status = bat(returnStatus: true, script: '@ci\\windows_bdba.bat ' + env.BDBA_CREDS_PSW + ' dist\\windows sdl_repo\\ovms-package') if (status != 0) { error "Error: Windows BDBA scan failed ${status}. Check win_bdba.log for details." } else { @@ -214,7 +214,7 @@ def bdba(){ def download_package(){ println "Downloading package from URL: ${env.PACKAGE_URL}" if(!fileExists('dist\\windows')){ - def status = bat(returnStatus: true, script: 'mkdir dist\\windows') + def status = bat(returnStatus: true, script: '@mkdir dist\\windows') if (status != 0) { error "Error: Creating dist\\windows directory failed ${status}. Check pipeline.log for details." } else { @@ -223,7 +223,7 @@ def download_package(){ } dir('dist\\windows') { if(fileExists('ovms.zip')){ - def status_del = bat(returnStatus: true, script: 'del /f ovms.zip') + def status_del = bat(returnStatus: true, script: '@del /f ovms.zip') if (status_del != 0) { error "Error: Deleting existing ovms.zip failed ${status_del}. Check pipeline.log for details." } else { @@ -231,20 +231,20 @@ def download_package(){ } } if(fileExists('ovms')){ - def status_del = bat(returnStatus: true, script: 'rmdir /s /q ovms') + def status_del = bat(returnStatus: true, script: '@rmdir /s /q ovms') if (status_del != 0) { error "Error: Deleting existing ovms directory failed ${status_del}. Check pipeline.log for details." } else { echo "Existing ovms directory deleted successfully." } } - def status = bat(returnStatus: true, script: 'curl -L -k -o ovms.zip ' + env.PACKAGE_URL) + def status = bat(returnStatus: true, script: '@curl -L -k -o ovms.zip ' + env.PACKAGE_URL) if (status != 0) { error "Error: Downloading package failed ${status}. Check pipeline.log for details." } else { echo "Package downloaded successfully." } - def status_unzip = bat(returnStatus: true, script: 'tar -xf ovms.zip') + def status_unzip = bat(returnStatus: true, script: '@tar -xf ovms.zip') if (status_unzip != 0) { error "Error: Unzipping package failed: ${status_unzip}." } else { @@ -256,44 +256,64 @@ def download_package(){ def unit_test(){ println "OVMS_PYTHON_ENABLED=${env.OVMS_PYTHON_ENABLED}" def pythonOption = env.OVMS_PYTHON_ENABLED == "0" ? "--no_python" : "--with_python" - status = bat(returnStatus: true, script: 'windows_test.bat ' + get_short_bazel_path() + ' ' + pythonOption) - if (status != 0) { - error "Error: Windows build test failed ${status}. Check win_build_test.log for details." - } else { - echo "Build successful." - } - status = bat(returnStatus: true, script: 'grep -A 4 bazel-bin/src/ovms_test.exe win_build_test.log | grep "Build completed successfully"') + boolean hasError = false + def errorReasons = [] + + def status = bat(returnStatus: true, script: '@windows_test.bat ' + get_short_bazel_path() + ' ' + pythonOption) if (status != 0) { - error "Error: Windows build test failed ${status}. Check win_build_test.log for details." + hasError = true + errorReasons << "Windows build test failed ${status}. Check win_build_test.log for details." } else { echo "Build successful." } -} -def check_tests(){ - def status = bat(returnStatus: true, script: 'grep " OK " win_test_summary.log') + status = bat(returnStatus: true, script: '@grep " OK " win_test_summary.log') if (status != 0) { - error "Error: Windows run test failed ${status}. Expecting passed tests and no passed tests detected. Check win_test_summary.log for details." + hasError = true + errorReasons << "Windows run test failed ${status}. Expecting passed tests and no passed tests detected. Check win_test_summary.log for details." } else { - def passed = bat(returnStatus: false, returnStdout: true, script: 'grep " OK " win_test_summary.log | wc -l') - echo "Success: Windows run test passed ${status}. ${passed} passed tests . Check win_test_summary.log for details." + def passed = bat(returnStatus: false, returnStdout: true, script: '@grep " OK " win_test_summary.log | wc -l').trim() + echo "Detected ${passed} passed tests . Check win_test_summary.log for details." } - status = bat(returnStatus: true, script: 'grep " FAILED " win_test_summary.log') + status = bat(returnStatus: true, script: '@grep -a -E "^\\[ FAILED \\].*[(][0-9]+ ms[)]$" win_full_test.log') if (status == 0) { - def failed = bat(returnStatus: false, returnStdout: true, script: 'grep " FAILED " win_test_summary.log | wc -l') - error "Error: Windows run test failed ${status}. ${failed} failed tests . Check win_test_summary.log for details." + hasError = true + def failed = bat(returnStatus: false, returnStdout: true, script: '@grep -a -E "^\\[ FAILED \\].*[(][0-9]+ ms[)]$" win_full_test.log | awk "!seen[$0]++" | wc -l').trim() + def failedTestsList = bat(returnStatus: false, returnStdout: true, script: '@grep -a -E "^\\[ FAILED \\].*[(][0-9]+ ms[)]$" win_full_test.log | awk "!seen[$0]++"').trim() + errorReasons << "Windows run test failed with ${failed} failed tests. Failed tests:\n${failedTestsList}\nCheck win_test_summary.log for details." } else { - echo "Run test no FAILED detected." + echo "no FAILED tests detected." } - status = bat(returnStatus: true, script: 'grep " PASSED " win_full_test.log') + // PASSED/crash detection is handled by windows_test.bat (via windows_parse_tests.bat). + // The bat exit code is the authoritative signal; win_test_summary.log contains diagnostics. + status = bat(returnStatus: true, script: '@grep -a "\\[ PASSED \\] " win_full_test.log') if (status != 0) { - error "Error: Windows run test failed ${status}. Expecting PASSED at the end of log. Check pipeline.log for details." + hasError = true + def markerLine = bat(returnStatus: true, script: '@grep -n "Check tests summary in" win_test_summary.log | head -1') + def summaryContent + if (markerLine == 0) { + summaryContent = bat(returnStatus: false, returnStdout: true, script: '@grep -n "Check tests summary in" win_test_summary.log | head -1 | cut -d: -f1 | xargs -I{} head -{} win_test_summary.log').trim() + } else { + summaryContent = bat(returnStatus: false, returnStdout: true, script: '@head -150 win_test_summary.log').trim() + } + errorReasons << "Windows run test failed. PASSED summary not found in win_full_test.log.\n${summaryContent}" } else { echo "Success: Windows run test finished with success." } + status = bat(returnStatus: true, script: '@grep -A 4 bazel-bin/src/ovms_test.exe win_build_test.log | grep "Build completed successfully"') + if (status != 0) { + hasError = true + errorReasons << "Windows build test failed ${status}. Check win_build_test.log for details." + } else { + echo "Build successful." + } + + if (hasError) { + error "Error: unit_test() detected failures:\n- " + errorReasons.join("\n- ") + } } // Post build steps @@ -329,13 +349,13 @@ def setup_bazel_remote_cache(){ def content = "build --remote_cache=\"${bazel_remote_cache_url}\"" def filePath = '.user.bazelrc' def command = "echo ${content} > ${filePath}" - status = bat(returnStatus: true, script: command) + status = bat(returnStatus: true, script: '@' + command) if ( status != 0) { println "Failed to set up bazel remote cache for Windows" return } command = "cat ${filePath}" - status = bat(returnStatus: true, script: command) + status = bat(returnStatus: true, script: '@' + command) if ( status != 0) { println "Failed to read file" return diff --git a/spelling-whitelist.txt b/spelling-whitelist.txt index 85763a1742..4bac06c568 100644 --- a/spelling-whitelist.txt +++ b/spelling-whitelist.txt @@ -29,4 +29,11 @@ demos/vlm_npu/README.md:157: mane ==> main, many, maine demos/vlm_npu/README.md:218: mane ==> main, many, maine demos/integration_with_OpenWebUI/README.md:423: Buildin ==> Building, Build in src/test/llm/output_parsers/lfm2_output_parser_test.cpp +windows_parse_tests.bat:35: seh ==> she +windows_parse_tests.bat:119: SEH ==> SHE +windows_parse_tests.bat:123: SEH ==> SHE +windows_parse_tests.bat:134: SEH ==> SHE +windows_parse_tests.bat:136: SEH ==> SHE +windows_parse_tests.bat:141: SEH ==> SHE +windows_parse_tests.bat:144: SEH ==> SHE src/test/llm/output_parsers/gemma4_output_parser_test.cpp diff --git a/src/test/schema_test.cpp b/src/test/schema_test.cpp index c97d6cfe2e..2b5aaf11fd 100644 --- a/src/test/schema_test.cpp +++ b/src/test/schema_test.cpp @@ -1155,7 +1155,6 @@ TEST(SchemaTest, ModelConfigPluginConfigPositive) { } ] })"; - rapidjson::Document modelConfigSeqNegativeDoc; modelConfigSeqNegativeDoc.Parse(modelConfigTimeoutNegative); auto result = ovms::validateJsonAgainstSchema(modelConfigSeqNegativeDoc, ovms::MODELS_CONFIG_SCHEMA.c_str()); diff --git a/windows_install_build_dependencies.bat b/windows_install_build_dependencies.bat index 7f32d6143c..91df6decff 100644 --- a/windows_install_build_dependencies.bat +++ b/windows_install_build_dependencies.bat @@ -466,7 +466,7 @@ if !errorlevel! neq 0 exit /b !errorlevel! %python_path%\python.exe -m pip install --upgrade pip if !errorlevel! neq 0 exit /b !errorlevel! :: setuptools<60.0 required for numpy1.23 on python311 to install -%python_path%\python.exe -m pip install "numpy==2.2.5" "Jinja2==3.1.6" "MarkupSafe==3.0.2" +%python_path%\python.exe -m pip install "numpy==2.2.5" "Jinja2==3.1.6" "MarkupSafe==3.0.2" "pytest==8.3.5" if !errorlevel! neq 0 exit /b !errorlevel! echo [INFO] Python %python_version% installed: %python_path% goto install_curl diff --git a/windows_parse_tests.bat b/windows_parse_tests.bat new file mode 100644 index 0000000000..c4b28e3658 --- /dev/null +++ b/windows_parse_tests.bat @@ -0,0 +1,168 @@ +:: +:: Copyright (c) 2026 Intel Corporation +:: +:: Licensed under the Apache License, Version 2.0 (the "License"); +:: you may not use this file except in compliance with the License. +:: You may obtain a copy of the License at +:: +:: http:::www.apache.org/licenses/LICENSE-2.0 +:: +:: Unless required by applicable law or agreed to in writing, software +:: distributed under the License is distributed on an "AS IS" BASIS, +:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +:: See the License for the specific language governing permissions and +:: limitations under the License. +:: +@echo off +setlocal EnableExtensions EnableDelayedExpansion + +set "fullLog=%~1" +set "summaryLog=%~2" +if not defined fullLog set "fullLog=win_full_test.log" +if not defined summaryLog set "summaryLog=win_test_summary.log" + +if not exist "!fullLog!" ( + echo [ERROR] Full test log not found: !fullLog! + exit /b 1 +) +if not exist "!summaryLog!" ( + echo [WARN] Summary log not found: !summaryLog! +) + +set "parserOutputTmp=!summaryLog!.parse.tmp" +set "summaryBackupTmp=!summaryLog!.orig.tmp" + +set "CRASH_PATTERN=segmentation fault\|segfault\|abnormal termination\|access violation\|sigsegv\|seh exception\|0xc0000005\|unknown file: error:" +set "FAILED_TEST_PATTERN=^\[ FAILED \].*([0-9][0-9]* ms)$" + +:: Check for per-test FAILED markers first - do not allow PASSED text to mask test failures +:: Match only timed gtest FAILED lines to avoid duplicates from the final "listed below" section. +grep -a -q "%FAILED_TEST_PATTERN%" "%fullLog%" +if !errorlevel! equ 0 goto :exit_build_error + +:: Also check for segmentation faults or crashes +grep -a -q -i "%CRASH_PATTERN%" "%fullLog%" +if !errorlevel! equ 0 goto :exit_build_error + +:: Consider the run successful only if the global PASSED summary is present anywhere in the log +:: (it can appear hundreds of lines before EOF due to SKIPPED and debug trace lines following it) +grep -a -q "\[ PASSED \] [0-9]" "%fullLog%" +if !errorlevel! equ 0 exit /b 0 + +:: If we reach here, tests did not complete correctly (no FAILED/crash marker but no final PASSED summary) +goto :exit_build_error + +:exit_build_error +if exist "%parserOutputTmp%" del /f /q "%parserOutputTmp%" +if exist "%summaryBackupTmp%" del /f /q "%summaryBackupTmp%" + +set "segfaultDetected=0" +grep -a -q -i "%CRASH_PATTERN%" "!fullLog!" +if !errorlevel! equ 0 set "segfaultDetected=1" + +( +echo. +echo [ERROR] FAILED TESTS OR CRASHES DETECTED: +echo. +echo === Failed Tests ^(from summary/full log^) === +grep -a "!FAILED_TEST_PATTERN!" "!fullLog!" | awk "!seen[$0]++" +echo. +echo === Last Successful Test === +grep -a " OK ]" "!fullLog!" | tail -1 +echo. +if "!segfaultDetected!"=="1" ( + echo === Last Running Test ^(likely the one that failed^) === + set "lastRunEntry=" + for /F "delims=" %%A in ('grep -a "\[ RUN" "!fullLog!" ^| tail -1') do ( + set "lastRunEntry=%%A" + ) + if defined lastRunEntry ( + echo !lastRunEntry! + ) else ( + echo [WARN] No gtest RUN marker found in !fullLog!. + ) + echo. + echo === Output from Last Running Test to End of Log === + set "lastRunLine=" + for /F "tokens=1 delims=:" %%A in ('grep -a -n "\[ RUN" "!fullLog!" ^| tail -1') do ( + set "lastRunLine=%%A" + ) + if defined lastRunLine ( + sed -n "!lastRunLine!,$p" "!fullLog!" | head -120 + ) else ( + echo [WARN] Could not determine last RUN line. Showing recent RUN markers and log tail. + grep -a -n "\[ RUN" "!fullLog!" | tail -3 + echo. + tail -20 "!fullLog!" + ) + echo. +) +echo === Context Around First FAILED Test === +set "firstFailedLine=" +set "firstFailedRunLine=" +for /F "tokens=1 delims=:" %%A in ('grep -a -n "!FAILED_TEST_PATTERN!" "!fullLog!" ^| head -1') do ( + set "firstFailedLine=%%A" +) +if defined firstFailedLine ( + for /F "tokens=1 delims=:" %%B in ('sed -n "1,!firstFailedLine!p" "!fullLog!" ^| grep -a -n "\[ RUN" ^| tail -1') do ( + set "firstFailedRunLine=%%B" + ) + if defined firstFailedRunLine ( + sed -n "!firstFailedRunLine!,$p" "!fullLog!" | head -160 + ) else ( + echo [WARN] Could not determine RUN line for first FAILED test. + ) +) else ( + echo [INFO] No per-test FAILED entry with timing found. +) +echo. +echo === SEH/Access Violation Context === +set "firstSehLine=" +set "firstSehRunLine=" +set "firstSehRunEntry=" +for /F "tokens=1 delims=:" %%A in ('grep -a -n -i "unknown file: error: SEH exception\|0xc0000005\|access violation\|SEH exception" "!fullLog!" ^| head -1') do ( + set "firstSehLine=%%A" +) +if defined firstSehLine ( + for /F "tokens=1 delims=:" %%B in ('sed -n "1,!firstSehLine!p" "!fullLog!" ^| grep -a -n "\[ RUN" ^| tail -1') do ( + set "firstSehRunLine=%%B" + ) + for /F "delims=" %%C in ('sed -n "1,!firstSehLine!p" "!fullLog!" ^| grep -a "\[ RUN" ^| tail -1') do ( + set "firstSehRunEntry=%%C" + ) + if defined firstSehRunEntry ( + echo [INFO] Unit test at first SEH marker: !firstSehRunEntry! + ) else ( + echo [WARN] Could not determine unit test name at first SEH marker. + ) + if defined firstSehRunLine ( + sed -n "!firstSehRunLine!,$p" "!fullLog!" | head -160 + ) else ( + echo [WARN] Could not determine RUN line for SEH exception entry. + ) +) else ( + echo [INFO] No SEH/Access Violation entry found. +) +echo. +echo === Segfault/Crash Messages ^(if any^) === +grep -a -i "%CRASH_PATTERN%\|stack trace" "!fullLog!" || echo ^(none found^) +echo. +echo [ERROR] Check tests summary in '!summaryLog!' and tests logs in '!fullLog!'. Rerun failed test with: windows_setupvars.bat and %cd%\bazel-bin\src\ovms_test.exe --gtest_filter='*.*' +) > "!parserOutputTmp!" 2>&1 + +if exist "!summaryLog!" ( + copy /Y "!summaryLog!" "!summaryBackupTmp!" > nul +) else ( + type nul > "!summaryBackupTmp!" +) + +( + type "%parserOutputTmp%" + echo. + type "%summaryBackupTmp%" +) > "%summaryLog%" + +if exist "!parserOutputTmp!" del /f /q "!parserOutputTmp!" +if exist "!summaryBackupTmp!" del /f /q "!summaryBackupTmp!" + +exit /b 1 diff --git a/windows_test.bat b/windows_test.bat index dfd1a8c424..e6b5ca965d 100644 --- a/windows_test.bat +++ b/windows_test.bat @@ -98,10 +98,12 @@ if !errorlevel! neq 0 exit /b !errorlevel! :: Run install_ovms_service.bat unit tests echo Running install_ovms_service.bat unit tests... -python -m pytest tests\python\test_install_ovms_service_windows.py -v 2>&1 | tee win_install_service_test.log -if !errorlevel! neq 0 ( +python -m pytest tests\python\test_install_ovms_service_windows.py -v > win_install_service_test.log 2>&1 +set "pytestExitCode=!errorlevel!" +type win_install_service_test.log +if !pytestExitCode! neq 0 ( echo [ERROR] install_ovms_service.bat unit tests failed. See win_install_service_test.log. - exit /b !errorlevel! + exit /b !pytestExitCode! ) echo [INFO] install_ovms_service.bat unit tests passed. @@ -113,13 +115,34 @@ echo Running: %runTest% set regex="\[ .* ms" set sed_clean="s/ (.* ms)//g" C:\Windows\System32\tar.exe -a -c -f win_test_log.zip win_full_test.log -grep -a %regex% win_full_test.log | sed %sed_clean% > win_test_summary.log -grep -a %regex% win_full_test.log | sed %sed_clean% | grep -q " FAILED " -if !errorlevel! equ 0 goto :exit_build_error -:exit_build + +:: Create summary log with filtered results, always create the file even if grep finds no matches + grep -a %regex% win_full_test.log > win_test_summary.tmp + if !errorlevel! equ 0 ( + sed %sed_clean% win_test_summary.tmp > win_test_summary.log 2>&1 + ) else ( + echo No matching test results found > win_test_summary.log + ) + if exist win_test_summary.tmp del /f /q win_test_summary.tmp + +:: Parse logs and decide final test status using dedicated parser script +:: Skip expensive parsing only if PASSED summary exists AND no FAILED markers +grep -a -q "\[ PASSED \] " win_full_test.log +set "hasPassed=!errorlevel!" +grep -a -q "\[ FAILED \] " win_full_test.log +set "hasFailed=!errorlevel!" +if !hasPassed! equ 0 if !hasFailed! neq 0 ( + echo [INFO] Tests finished with no failures. Check the summary in win_test_summary.log. + exit /b 0 +) +call %cd%\windows_parse_tests.bat win_full_test.log win_test_summary.log +set "parseExitCode=!errorlevel!" +if !parseExitCode! neq 0 exit /b !parseExitCode! + echo [INFO] Tests finished with no failures. Check the summary in win_test_summary.log. exit /b 0 + :exit_build_error -echo [ERROR] Check tests summary in 'win_test_summary.log' and tests logs in 'win_full_test.log'. Rerun failed test with: windows_setupvars.bat and %cd%\bazel-bin\src\ovms_test.exe --gtest_filter='*.*' +echo [ERROR] windows_test.bat failed before test parsing stage. exit /b 1 endlocal