Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions Build/scripts/Invoke-CppTest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ Initialize-VsDevEnvironment
# Suppress assertion dialog boxes (DebugProcs.dll checks this env var)
# This prevents tests from blocking on MessageBox popups
$env:AssertUiEnabled = 'false'
# Unconditional test-mode override: bypasses registry AssertMessageBox key in DebugProcs.dll
$env:FW_TEST_MODE = '1'

# Suppress Windows Error Reporting and crash dialogs
# SEM_FAILCRITICALERRORS = 0x0001
Expand Down Expand Up @@ -549,6 +551,13 @@ function Invoke-Run {
}

$process.WaitForExit()
$nativeExitCode = $null
try {
$nativeExitCode = $process.ExitCode
}
catch {
$nativeExitCode = $null
}

$logTail = @()
if (Test-Path $LogPath) {
Expand All @@ -562,15 +571,34 @@ function Invoke-Run {
Write-Host "--- end output ---" -ForegroundColor Yellow
}

# Determine exit code: parse the Unit++ summary line from the log as the authoritative
# source. Start-Process -RedirectStandardOutput in PowerShell 5.1 can return a null
# ExitCode even after WaitForExit(), so the process exit code is not reliable here.
# Determine exit code using both the real process exit code and the Unit++ summary.
# The process exit code is authoritative for crashes/teardown failures that occur after
# the Unit++ summary has already been written.
$exitCode = -1
$summaryExitCode = $null
if (-not $timedOut) {
$summaryLine = $logTail | Where-Object { $_ -match 'Tests \[Ok-Fail-Error\]: \[\d+-\d+-\d+\]' } | Select-Object -Last 1
if ($summaryLine) {
$m = [regex]::Match($summaryLine, 'Tests \[Ok-Fail-Error\]: \[(\d+)-(\d+)-(\d+)\]')
$exitCode = [int]$m.Groups[2].Value + [int]$m.Groups[3].Value
$summaryExitCode = [int]$m.Groups[2].Value + [int]$m.Groups[3].Value
}

if ($terminatedAfterCompletion) {
if ($null -ne $nativeExitCode -and $nativeExitCode -ne 0) {
$exitCode = $nativeExitCode
}
else {
$exitCode = 1
}
}
elseif ($null -ne $nativeExitCode -and $nativeExitCode -ne 0) {
$exitCode = $nativeExitCode
}
elseif ($null -ne $summaryExitCode) {
$exitCode = $summaryExitCode
}
elseif ($null -ne $nativeExitCode) {
$exitCode = $nativeExitCode
}
}

Expand Down
118 changes: 112 additions & 6 deletions Lib/src/unit++/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,86 @@
// Terms of use are in the file COPYING
#include "main.h"
#include <algorithm>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(WIN32) || defined(WIN64)
#define WINDOWS_LEAN_AND_MEAN
#include <Windows.h>
#include <crtdbg.h>
#endif
using namespace std;
using namespace unitpp;

#if defined(WIN32) || defined(WIN64)
namespace
{
void TerminateOnSigAbrt(int)
{
_exit(3);
}

typedef HRESULT (WINAPI * PfnWerGetFlags)(HANDLE, PDWORD);
typedef HRESULT (WINAPI * PfnWerSetFlags)(DWORD);

const DWORD kWerFaultReportingNoUi = 0x00000004;
const DWORD kWerFaultReportingAlwaysShowUi = 0x00000010;

void ConfigureWindowsErrorReportingUi()
{
DWORD errorMode = GetErrorMode();
errorMode |= SEM_FAILCRITICALERRORS;
errorMode |= SEM_NOGPFAULTERRORBOX;
errorMode |= SEM_NOOPENFILEERRORBOX;
SetErrorMode(errorMode);

HMODULE hWer = LoadLibraryA("wer.dll");
if (!hWer)
return;

PfnWerGetFlags pfnWerGetFlags = reinterpret_cast<PfnWerGetFlags>(
GetProcAddress(hWer, "WerGetFlags")
);
PfnWerSetFlags pfnWerSetFlags = reinterpret_cast<PfnWerSetFlags>(
GetProcAddress(hWer, "WerSetFlags")
);

if (pfnWerSetFlags)
{
DWORD flags = 0;
if (pfnWerGetFlags)
pfnWerGetFlags(GetCurrentProcess(), &flags);

flags |= kWerFaultReportingNoUi;
flags &= ~kWerFaultReportingAlwaysShowUi;
pfnWerSetFlags(flags);
}

FreeLibrary(hWer);
}

void ConfigureCrtReportUi()
{
_set_error_mode(_OUT_TO_STDERR);

_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);

_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
}

void SuppressInteractiveCrashUi()
{
ConfigureWindowsErrorReportingUi();
ConfigureCrtReportUi();
}
}
#endif

bool unitpp::verbose = false;
int unitpp::verbose_lvl = 0;
bool unitpp::line_fmt = false;
Expand All @@ -25,6 +101,9 @@ void unitpp::set_tester(test_runner* tr)

int main(int argc, const char* argv[])
{
#if defined(WIN32) || defined(WIN64)
SuppressInteractiveCrashUi();
#endif
printf("DEBUG: unit++ main start\n"); fflush(stdout);
options().add("v", new options_utils::opt_flag(verbose));
options().alias("verbose", "v");
Expand All @@ -42,14 +121,41 @@ int main(int argc, const char* argv[])
if (!runner)
runner = &plain;

printf("DEBUG: Calling GlobalSetup\n"); fflush(stdout);
GlobalSetup(verbose);
printf("DEBUG: Returned from GlobalSetup\n"); fflush(stdout);
int retval = 0;

int retval = runner->run_tests(argc, argv) ? 0 : 1;
try {
printf("DEBUG: Calling GlobalSetup\n"); fflush(stdout);
GlobalSetup(verbose);
printf("DEBUG: Returned from GlobalSetup\n"); fflush(stdout);
}
catch (const std::exception& e) {
fprintf(stderr, "GlobalSetup threw std::exception: %s\n", e.what());
fflush(stderr);
return 1;
}
catch (...) {
fprintf(stderr, "GlobalSetup threw an unknown exception\n");
fflush(stderr);
return 1;
}

retval = runner->run_tests(argc, argv) ? 0 : 1;
signal(SIGABRT, TerminateOnSigAbrt);

printf("DEBUG: Calling GlobalTeardown\n"); fflush(stdout);
GlobalTeardown();
try {
printf("DEBUG: Calling GlobalTeardown\n"); fflush(stdout);
GlobalTeardown();
}
catch (const std::exception& e) {
fprintf(stderr, "GlobalTeardown threw std::exception: %s\n", e.what());
fflush(stderr);
retval = 1;
}
catch (...) {
fprintf(stderr, "GlobalTeardown threw an unknown exception\n");
fflush(stderr);
retval = 1;
}
printf("DEBUG: unit++ main end (retval=%d)\n", retval); fflush(stdout);
return retval;
}
Expand Down
38 changes: 20 additions & 18 deletions Src/AppForTests.config
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.diagnostics>
<trace autoflush="false" indentsize="4">
<listeners>
<clear/>
<add name="FwTraceListener" type="SIL.LCModel.Utils.EnvVarTraceListener, SIL.LCModel.Utils, Version=11.0.0.0, Culture=neutral"
initializeData="assertuienabled='false' assertexceptionenabled='true' logfilename='%temp%/asserts.log'"/>
</listeners>
</trace>
</system.diagnostics>
<runtime>
<generatePublisherEvidence enabled="false" />
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- Binding redirects are auto-generated at build time
(AutoGenerateBindingRedirects=true in Directory.Build.props).
Do NOT add manual redirects here; they will drift from CPM versions. -->
</assemblyBinding>
</runtime>
<system.diagnostics>
<trace autoflush="false" indentsize="4">
<listeners>
<clear />
<add
name="FwTraceListener"
type="SIL.LCModel.Utils.EnvVarTraceListener, SIL.LCModel.Utils, Version=11.0.0.0, Culture=neutral"
initializeData="assertuienabled='false' assertexceptionenabled='true' logfilename='%temp%/asserts.log'" />
</listeners>
</trace>
</system.diagnostics>
<runtime>
<generatePublisherEvidence enabled="false" />
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- Binding redirects are auto-generated at build time
(AutoGenerateBindingRedirects=true in Directory.Build.props).
Do NOT add manual redirects here; they will drift from CPM versions. -->
</assemblyBinding>
</runtime>
</configuration>
6 changes: 6 additions & 0 deletions Src/AssemblyInfoForTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
// Set stub for messagebox so that we don't pop up a message box when running tests.
[assembly: SetMessageBoxAdapter]

// Log last-chance managed exceptions to console output before process termination.
[assembly: LogUnhandledExceptions]

// Suppress all assertion dialog boxes (native + managed) regardless of config file coverage
[assembly: SuppressAssertDialogs]

// Cleanup all singletons after running tests
[assembly: CleanupSingletons]

Expand Down
6 changes: 6 additions & 0 deletions Src/AssemblyInfoForUiIndependentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
// This file is for test fixtures for UI independent projects, i.e. projects that don't
// reference System.Windows.Forms et al.

// Log last-chance managed exceptions to console output before process termination.
[assembly: LogUnhandledExceptions]

// Suppress all assertion dialog boxes (native + managed) regardless of config file coverage
[assembly: SuppressAssertDialogs]

// Cleanup all singletons after running tests
[assembly: CleanupSingletons]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public override void AfterTest(ITest test)

private void OnThreadException(object sender, ThreadExceptionEventArgs e)
{
Console.Error.WriteLine("Unhandled Windows Forms thread exception during test run:");
Console.Error.WriteLine(e.Exception.ToString());
Console.Error.Flush();

throw new ApplicationException(e.Exception.Message, e.Exception);
}
}
Expand Down
Loading
Loading