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
35 changes: 20 additions & 15 deletions include/dxc/Test/WEXAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,26 @@ HRESULT TryGetValue(const wchar_t *param, Common::String &retStr);
} // namespace TestExecution
namespace Logging {
namespace Log {
inline void StartGroup(const wchar_t *name) {
wprintf(L"BEGIN TEST(S): <%ls>\n", name);
}
inline void EndGroup(const wchar_t *name) {
wprintf(L"END TEST(S): <%ls>\n", name);
}
inline void Comment(const wchar_t *msg) {
fputws(msg, stdout);
fputwc(L'\n', stdout);
}
inline void Error(const wchar_t *msg) {
fputws(msg, stderr);
fputwc(L'\n', stderr);
ADD_FAILURE();
}

// Implementations live in tools/clang/unittests/HLSLTestLib/WEXAdapterLog.cpp.
// Comment messages are buffered per test and only printed when the test fails;
// Error messages are printed immediately and flush any buffered comments. See
// FailurePrinter in tools/clang/unittests/HLSL/TestMain.cpp for the listener
// that wires this up to GoogleTest's lifecycle.
void StartGroup(const wchar_t *name);
void EndGroup(const wchar_t *name);
void Comment(const wchar_t *msg);
void Error(const wchar_t *msg);

// Accessors used by the gtest event listener. Not for direct use by tests.
const char *GetBufferedLog();
void ClearBufferedLog();
bool HasBufferedLog();
// Called by the FailurePrinter listener when GoogleTest records a part
// failure; attributes that failure to the currently open StartGroup scope,
// if any.
void NotifyTestPartFailed();

} // namespace Log
} // namespace Logging
} // namespace WEX
Expand Down
47 changes: 46 additions & 1 deletion tools/clang/unittests/HLSL/TestMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
#include "HLSLTestOptions.h"
#include "dxc/Test/WEXAdapter.h"

#include <clocale>
#include <cstdio>
#include <cstring>

#if defined(_WIN32)
#include <windows.h>
#if defined(_MSC_VER)
Expand Down Expand Up @@ -56,16 +60,49 @@ class FailurePrinter : public TestEventListener {
void OnTestStart(const TestInfo &ti) override {
// Do not output on test start
// defaultListener->OnTestStart(ti);
#ifndef _WIN32
// Each test starts with an empty log buffer; clear any leftovers from a
// previous test that may have failed before reaching the end-of-test
// flush path.
WEX::Logging::Log::ClearBufferedLog();
#endif
}

void OnTestPartResult(const TestPartResult &result) override {
#ifndef _WIN32
if (result.failed()) {
// Attribute the failure to any currently open WEX StartGroup scope.
WEX::Logging::Log::NotifyTestPartFailed();
// Suppress the gtest-default "WEXAdapterLog.cpp:N Failure / Failed"
// noise; the failing group name and Error text from the WEX log are
// what's actually informative.
const char *FileName = result.file_name();
if (FileName && std::strstr(FileName, "WEXAdapterLog.cpp"))
return;
}
#endif
defaultListener->OnTestPartResult(result);
}

void OnTestEnd(const TestInfo &ti) override {
// Only output if failure on test end
if (ti.result()->Failed())
if (ti.result()->Failed()) {
#ifndef _WIN32
// Flush any comments emitted after the last failing assertion.
if (WEX::Logging::Log::HasBufferedLog()) {
std::fputs(WEX::Logging::Log::GetBufferedLog(), stderr);
WEX::Logging::Log::ClearBufferedLog();
}
#endif
defaultListener->OnTestEnd(ti);
}
#ifndef _WIN32
else {
// Test passed -- discard the buffered comments so they don't leak into
// the next test.
WEX::Logging::Log::ClearBufferedLog();
}
#endif
}

void OnTestCaseEnd(const TestCase &tc) override {
Expand Down Expand Up @@ -108,6 +145,14 @@ const char *TestMainArgv0;
int main(int argc, char **argv) {
llvm::sys::PrintStackTraceOnErrorSignal(true /* Disable crash reporting */);

#ifndef _WIN32
// Pick up the user's locale so wcstombs can convert wide log strings to
// UTF-8 in WEXAdapterLog.cpp. Without this, fputws/%ls silently drop wide
// output in the default "C" locale, hiding the WEX::Logging::Log::Comment
// diagnostics that tests rely on for context on failure.
std::setlocale(LC_ALL, "");
#endif

for (int i = 1; i < argc; ++i) {
ARG_LIST(SAVE_ARG)
}
Expand Down
1 change: 1 addition & 0 deletions tools/clang/unittests/HLSLTestLib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ add_clang_library(HLSLTestLib
DxcTestUtils.cpp
FileCheckerTest.cpp
FileCheckForTest.cpp
WEXAdapterLog.cpp
)

add_dependencies(HLSLTestLib TablegenHLSLOptions)
Expand Down
142 changes: 142 additions & 0 deletions tools/clang/unittests/HLSLTestLib/WEXAdapterLog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
///////////////////////////////////////////////////////////////////////////////
// //
// WEXAdapterLog.cpp //
// //
// Implements the WEX::Logging::Log functions declared in WEXAdapter.h for //
// non-Windows builds. //
// //
// Logging model: //
// * Comment() and Error() append to a per-test buffer. //
// * StartGroup()/EndGroup() bracket a sub-test scope; messages and //
// failures inside a group are scoped to that group. When EndGroup() //
// fires, the group's content is appended to the outer buffer only if //
// at least one failure was recorded inside it; otherwise it is //
// discarded. This matches the TAEF/WEX behavior on Windows. //
// * Error() records a non-fatal GoogleTest failure so the test is //
// marked failed; the originating file/line of the ADD_FAILURE is //
// suppressed by the FailurePrinter listener in TestMain.cpp so it //
// doesn't add noise -- the group name and Error message are what is //
// informative. //
// //
///////////////////////////////////////////////////////////////////////////////

#ifndef _WIN32

#include "dxc/Test/WEXAdapter.h"

#include <clocale>
#include <cstdio>
#include <cwchar>
#include <string>

namespace {

struct LogState {
std::string TestBuffer;
std::string GroupBuffer;
std::string GroupName;
unsigned GroupFailureCount = 0;
};

LogState &State() {
static LogState S;
return S;
}

std::string WideToUtf8(const wchar_t *Msg) {
if (!Msg)
return {};
std::mbstate_t MBState{};
const wchar_t *Src = Msg;
size_t Bytes = std::wcsrtombs(nullptr, &Src, 0, &MBState);
if (Bytes != static_cast<size_t>(-1)) {
std::string Result(Bytes, '\0');
Src = Msg;
MBState = {};
std::wcsrtombs(&Result[0], &Src, Bytes, &MBState);
return Result;
}
// Locale conversion unavailable -- copy ASCII verbatim, replace rest.
std::string Result;
for (const wchar_t *P = Msg; *P; ++P)
Result.push_back(*P < 0x80 ? static_cast<char>(*P) : '?');
return Result;
}

std::string &ActiveBuffer(LogState &S) {
return S.GroupName.empty() ? S.TestBuffer : S.GroupBuffer;
}

} // namespace

namespace WEX {
namespace Logging {
namespace Log {

void StartGroup(const wchar_t *Name) {
LogState &S = State();
// If a previous group was left open (missing EndGroup), surface its
// contents conservatively rather than dropping them.
if (!S.GroupName.empty()) {
if (S.GroupFailureCount > 0 || !S.GroupBuffer.empty())
S.TestBuffer.append(S.GroupBuffer);
S.GroupBuffer.clear();
S.GroupFailureCount = 0;
}
S.GroupName = WideToUtf8(Name);
}

void EndGroup(const wchar_t *Name) {
LogState &S = State();
if (S.GroupName.empty())
return;
if (S.GroupFailureCount > 0) {
S.TestBuffer.append("---- FAILED: ");
S.TestBuffer.append(S.GroupName);
S.TestBuffer.append(" ----\n");
S.TestBuffer.append(S.GroupBuffer);
}
S.GroupBuffer.clear();
S.GroupName.clear();
S.GroupFailureCount = 0;
(void)Name; // EndGroup's name is informational; group state is single-deep.
}

void Comment(const wchar_t *Msg) {
LogState &S = State();
ActiveBuffer(S).append(WideToUtf8(Msg));
ActiveBuffer(S).push_back('\n');
}

void Error(const wchar_t *Msg) {
LogState &S = State();
std::string MsgUtf8 = WideToUtf8(Msg);
ActiveBuffer(S).append("ERROR: ");
ActiveBuffer(S).append(MsgUtf8);
ActiveBuffer(S).push_back('\n');
if (!S.GroupName.empty())
++S.GroupFailureCount;
ADD_FAILURE();
}

const char *GetBufferedLog() { return State().TestBuffer.c_str(); }
bool HasBufferedLog() { return !State().TestBuffer.empty(); }
void ClearBufferedLog() {
LogState &S = State();
S.TestBuffer.clear();
S.GroupBuffer.clear();
S.GroupName.clear();
S.GroupFailureCount = 0;
}

void NotifyTestPartFailed() {
LogState &S = State();
if (!S.GroupName.empty())
++S.GroupFailureCount;
}

} // namespace Log
} // namespace Logging
} // namespace WEX

#endif // _WIN32
Loading