diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2021-04-29 09:01:46 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-04-29 09:01:46 -0400 |
| commit | 972bd3c4c24b06501c52127416afb763a066b8ad (patch) | |
| tree | e3874d4952ac557d5c323bb1e43be4584c100afc | |
| parent | 541d1cab81d895c406fc33cb476e37ce8a6a9702 (diff) | |
Support for escaped paths in tools (#1823)
* #include an absolute path didn't work - because paths were taken to always be relative.
* Split out StringEscapeUtil.
* Added StringEscapeUtil.
* Fix typo in unix quoting type.
* Small comment improvements.
* Try to fix linux linking issue.
* Fix typo.
* Attempt to fix linux link issue.
* Update VS proj even though nothing really changed.
* Fix another typo issue.
* Fix for windows issue.
Fixed bug.
* Make separate Utils for escaping.
* Fix typo.
* Split out into StringEscapeHandler.
* Windows shell does handle removing quotes (so remove code to remove them).
* Handle unescaping if not initiating using the shell.
* Slight improvement around shell like decoding.
* Simplify command extraction.
* Add shared-library category type.
* Fix bug in command extraction.
* Typo in transcendental category.
* Enable unit-test on in smoke test category.
* Make parsing failing output as a failing test.
* Fixes for transcendental tests. Disable tests that do not work.
* Changed category parsing.
* Removed the TestResult parameter from _gatherTestsForFile.
Made testsList only output.
* Remove testing if all tests were disabled.
* Fix typo.
* Disable path canonical test on linux because CI issue.
21 files changed, 962 insertions, 309 deletions
diff --git a/build/visual-studio/core/core.vcxproj b/build/visual-studio/core/core.vcxproj index 9d5be833b..022132f71 100644 --- a/build/visual-studio/core/core.vcxproj +++ b/build/visual-studio/core/core.vcxproj @@ -210,6 +210,7 @@ <ClInclude Include="..\..\..\source\core\slang-smart-pointer.h" /> <ClInclude Include="..\..\..\source\core\slang-std-writers.h" /> <ClInclude Include="..\..\..\source\core\slang-stream.h" /> + <ClInclude Include="..\..\..\source\core\slang-string-escape-util.h" /> <ClInclude Include="..\..\..\source\core\slang-string-slice-pool.h" /> <ClInclude Include="..\..\..\source\core\slang-string-util.h" /> <ClInclude Include="..\..\..\source\core\slang-string.h" /> @@ -245,6 +246,7 @@ <ClCompile Include="..\..\..\source\core\slang-shared-library.cpp" /> <ClCompile Include="..\..\..\source\core\slang-std-writers.cpp" /> <ClCompile Include="..\..\..\source\core\slang-stream.cpp" /> + <ClCompile Include="..\..\..\source\core\slang-string-escape-util.cpp" /> <ClCompile Include="..\..\..\source\core\slang-string-slice-pool.cpp" /> <ClCompile Include="..\..\..\source\core\slang-string-util.cpp" /> <ClCompile Include="..\..\..\source\core\slang-string.cpp" /> diff --git a/build/visual-studio/core/core.vcxproj.filters b/build/visual-studio/core/core.vcxproj.filters index 1b13ff325..93db8cb97 100644 --- a/build/visual-studio/core/core.vcxproj.filters +++ b/build/visual-studio/core/core.vcxproj.filters @@ -129,6 +129,9 @@ <ClInclude Include="..\..\..\source\core\slang-stream.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\..\..\source\core\slang-string-escape-util.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="..\..\..\source\core\slang-string-slice-pool.h"> <Filter>Header Files</Filter> </ClInclude> @@ -230,6 +233,9 @@ <ClCompile Include="..\..\..\source\core\slang-stream.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\..\..\source\core\slang-string-escape-util.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="..\..\..\source\core\slang-string-slice-pool.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/build/visual-studio/slang-cpp-extractor/slang-cpp-extractor.vcxproj b/build/visual-studio/slang-cpp-extractor/slang-cpp-extractor.vcxproj index 707327ba7..fc15a6e7d 100644 --- a/build/visual-studio/slang-cpp-extractor/slang-cpp-extractor.vcxproj +++ b/build/visual-studio/slang-cpp-extractor/slang-cpp-extractor.vcxproj @@ -186,12 +186,12 @@ <ClCompile Include="..\..\..\tools\slang-cpp-extractor\unit-test.cpp" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\core\core.vcxproj"> - <Project>{F9BE7957-8399-899E-0C49-E714FDDD4B65}</Project> - </ProjectReference> <ProjectReference Include="..\compiler-core\compiler-core.vcxproj"> <Project>{12C1E89D-F5D0-41D3-8E8D-FB3F358F8126}</Project> </ProjectReference> + <ProjectReference Include="..\core\core.vcxproj"> + <Project>{F9BE7957-8399-899E-0C49-E714FDDD4B65}</Project> + </ProjectReference> </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> diff --git a/premake5.lua b/premake5.lua index c191f5ec6..062835109 100644 --- a/premake5.lua +++ b/premake5.lua @@ -719,7 +719,7 @@ tool "slang-cpp-extractor" uuid "CA8A30D1-8FA9-4330-B7F7-84709246D8DC" includedirs { "." } - links { "core", "compiler-core" } + links { "compiler-core", "core" } -- -- `slang-generate` is a tool we use for source code generation on diff --git a/source/compiler-core/slang-nvrtc-compiler.cpp b/source/compiler-core/slang-nvrtc-compiler.cpp index 7a8d5bcfd..79ece266b 100644 --- a/source/compiler-core/slang-nvrtc-compiler.cpp +++ b/source/compiler-core/slang-nvrtc-compiler.cpp @@ -824,7 +824,7 @@ SlangResult NVRTCDownstreamCompiler::compile(const CompileOptions& options, RefP } ScopeProgram scope(this, program); - List<const char*> dstOptions; + List<const char*> dstOptions; dstOptions.setCount(cmdLine.m_args.getCount()); for (Index i = 0; i < cmdLine.m_args.getCount(); ++i) { diff --git a/source/compiler-core/slang-source-loc.cpp b/source/compiler-core/slang-source-loc.cpp index 4b589fbf3..29fc0465a 100644 --- a/source/compiler-core/slang-source-loc.cpp +++ b/source/compiler-core/slang-source-loc.cpp @@ -2,6 +2,7 @@ #include "slang-source-loc.h" #include "../core/slang-string-util.h" +#include "../core/slang-string-escape-util.h" namespace Slang { @@ -66,10 +67,7 @@ void PathInfo::appendDisplayName(StringBuilder& out) const case Type::FromString: case Type::FoundPath: { - - out.appendChar('"'); - StringUtil::appendEscaped(foundPath.getUnownedSlice(), out); - out.appendChar('"'); + StringEscapeUtil::appendQuoted(StringEscapeUtil::getHandler(StringEscapeUtil::Style::Cpp), foundPath.getUnownedSlice(), out); break; } default: break; diff --git a/source/core/slang-process-util.h b/source/core/slang-process-util.h index 5d8c358e8..d47a4793b 100644 --- a/source/core/slang-process-util.h +++ b/source/core/slang-process-util.h @@ -5,6 +5,8 @@ #include "slang-string.h" #include "slang-list.h" +#include "slang-string-escape-util.h" + namespace Slang { struct CommandLine @@ -80,6 +82,10 @@ struct ExecuteResult struct ProcessUtil { + /// The quoting style used for the command line on this target. Currently just uses Space, + /// but in future may take into account platform sec + static StringEscapeHandler* getEscapeHandler(); + /// Get the suffix used on this platform static UnownedStringSlice getExecutableSuffix(); @@ -89,9 +95,6 @@ struct ProcessUtil /// Execute the command line static SlangResult execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult); - /// Append text escaped for using on a command line - static void appendCommandLineEscaped(const UnownedStringSlice& slice, StringBuilder& out); - static uint64_t getClockFrequency(); static uint64_t getClockTick(); @@ -116,6 +119,8 @@ SLANG_INLINE Index CommandLine::findArgIndex(const UnownedStringSlice& slice) co // ----------------------------------------------------------------------- SLANG_INLINE void CommandLine::addPrefixPathArg(const char* prefix, const String& path, const char* pathPostfix) { + auto escapeHandler = ProcessUtil::getEscapeHandler(); + StringBuilder builder; builder << prefix; if (pathPostfix) @@ -123,12 +128,12 @@ SLANG_INLINE void CommandLine::addPrefixPathArg(const char* prefix, const String // Work out the path with the postfix StringBuilder fullPath; fullPath << path << pathPostfix; - ProcessUtil::appendCommandLineEscaped(fullPath.getUnownedSlice(), builder); + StringEscapeUtil::appendMaybeQuoted(escapeHandler, fullPath.getUnownedSlice(), builder); } else { - ProcessUtil::appendCommandLineEscaped(path.getUnownedSlice(), builder); - } + StringEscapeUtil::appendMaybeQuoted(escapeHandler, path.getUnownedSlice(), builder); + } // This arg doesn't need subsequent escaping addEscapedArg(builder); diff --git a/source/core/slang-string-escape-util.cpp b/source/core/slang-string-escape-util.cpp new file mode 100644 index 000000000..13fce6dc7 --- /dev/null +++ b/source/core/slang-string-escape-util.cpp @@ -0,0 +1,548 @@ +#include "slang-string-escape-util.h" + +#include "slang-char-util.h" +#include "slang-text-io.h" + +#include "../../slang-com-helper.h" + +namespace Slang { + +// !!!!!!!!!!!!!!!!!!!!!!!!!! SpaceStringEscapeHandler !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +class SpaceStringEscapeHandler : public StringEscapeHandler +{ +public: + typedef StringEscapeHandler Super; + + virtual bool isQuotingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE { return isEscapingNeeded(slice); } + + virtual bool isEscapingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE; + virtual SlangResult appendEscaped(const UnownedStringSlice& slice, StringBuilder& out) SLANG_OVERRIDE; + virtual SlangResult appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out) SLANG_OVERRIDE; + virtual SlangResult lexQuoted(const char* cursor, const char** outCursor) SLANG_OVERRIDE; + + SpaceStringEscapeHandler() : Super('"') {} +}; + +bool SpaceStringEscapeHandler::isEscapingNeeded(const UnownedStringSlice& slice) +{ + return slice.indexOf(' ') >= 0; +} + +SlangResult SpaceStringEscapeHandler::appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out) +{ + if (slice.indexOf('"') >= 0) + { + return SLANG_FAIL; + } + + out.append(slice); + return SLANG_OK; +} + +SlangResult SpaceStringEscapeHandler::appendEscaped(const UnownedStringSlice& slice, StringBuilder& out) +{ + if (slice.indexOf('"') >= 0) + { + return SLANG_FAIL; + } + out.append(slice); + return SLANG_OK; +} + +/* static */SlangResult SpaceStringEscapeHandler::lexQuoted(const char* cursor, const char** outCursor) +{ + *outCursor = cursor; + + if (*cursor != m_quoteChar) + { + return SLANG_FAIL; + } + cursor++; + + for (;;) + { + const char c = *cursor; + if (c == m_quoteChar) + { + *outCursor = cursor + 1; + return SLANG_OK; + } + switch (c) + { + case 0: + case '\n': + case '\r': + { + // Didn't hit closing quote! + return SLANG_FAIL; + } + default: + { + ++cursor; + break; + } + } + } +} + +// !!!!!!!!!!!!!!!!!!!!!!!!!! CppStringEscapeHandler !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +class CppStringEscapeHandler : public StringEscapeHandler +{ +public: + typedef StringEscapeHandler Super; + + virtual bool isQuotingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE { SLANG_UNUSED(slice); return true; } + virtual bool isEscapingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE; + virtual SlangResult appendEscaped(const UnownedStringSlice& slice, StringBuilder& out) SLANG_OVERRIDE; + virtual SlangResult appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out) SLANG_OVERRIDE; + virtual SlangResult lexQuoted(const char* cursor, const char** outCursor) SLANG_OVERRIDE; + + CppStringEscapeHandler() : Super('"') {} +}; + +static char _getHexChar(int v) +{ + return (v <= 9) ? char(v + '0') : char(v - 10 + 'A'); +} + +static int _getHexDigit(char c) +{ + if (c >= '0' && c <= '9') + { + return c - '0'; + } + else if (c >= 'a' && c <= 'f') + { + return c - 'a' + 10; + } + else if (c >= 'A' && c <= 'F') + { + return c - 'A' + 10; + } + else + { + SLANG_ASSERT(!"Not a hex digit"); + return 0; + } +} + +static char _getCppEscapedChar(char c) +{ + switch (c) + { + case '\b': return 'b'; + case '\f': return 'f'; + case '\n': return 'n'; + case '\r': return 'r'; + case '\a': return 'a'; + case '\t': return 't'; + case '\v': return 'v'; + case '\'': return '\''; + case '\"': return '"'; + case '\\': return '\\'; + default: return 0; + } +} + +static char _getCppUnescapedChar(char c) +{ + switch (c) + { + case 'b': return '\b'; + case 'f': return '\f'; + case 'n': return '\n'; + case 'r': return '\r'; + case 'a': return '\a'; + case 't': return '\t'; + case 'v': return '\v'; + case '\'': return '\''; + case '\"': return '"'; + case '\\': return '\\'; + default: return 0; + } +} + +/* static */bool CppStringEscapeHandler::isEscapingNeeded(const UnownedStringSlice& slice) +{ + const char* cur = slice.begin(); + const char*const end = slice.end(); + + for (; cur < end; ++cur) + { + const char c = *cur; + + switch (c) + { + case '\'': + case '\"': + case '\\': + { + // Strictly speaking ' shouldn't need a quote if in a C style string. + return true; + } + default: + { + if (c < ' ' || c >= 0x7e) + { + return true; + } + break; + } + } + } + return false; +} + +SlangResult CppStringEscapeHandler::appendEscaped(const UnownedStringSlice& slice, StringBuilder& out) +{ + const char* start = slice.begin(); + const char* cur = start; + const char*const end = slice.end(); + + for (; cur < end; ++cur) + { + const char c = *cur; + const char escapedChar = _getCppEscapedChar(c); + + if (escapedChar) + { + // Flush + if (start < cur) + { + out.append(start, cur); + } + out.appendChar('\\'); + out.appendChar(escapedChar); + + start = cur + 1; + } + else if (c < ' ' || c > 126) + { + // Flush + if (start < cur) + { + out.append(start, cur); + } + + char buf[5] = "\\0x0"; + + buf[3] = _getHexChar((int(c) >> 4) & 0xf); + buf[4] = _getHexChar(c & 0xf); + + out.append(buf, buf + 4); + + start = cur + 1; + } + } + + if (start < end) + { + out.append(start, end); + } + return SLANG_OK; +} + +SlangResult CppStringEscapeHandler::appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out) +{ + const char* start = slice.begin(); + const char* cur = start; + const char*const end = slice.end(); + + for (; cur < end; ++cur) + { + const char c = *cur; + + if (c == '\\') + { + // Flush + if (start < end) + { + out.append(start, end); + } + + /// Next + cur++; + + if (cur >= end) + { + return SLANG_FAIL; + } + + // Need to handle various escape sequence cases + switch (*cur) + { + case '\'': + case '\"': + case '\\': + case '?': + case 'a': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + case 'v': + { + const char unescapedChar = _getCppUnescapedChar(*cur); + if (unescapedChar == 0) + { + // Don't know how to unescape that char + return SLANG_FAIL; + } + out.appendChar(unescapedChar); + + start = cur + 1; + break; + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + { + // octal escape: up to 3 characters + ++cur; + int value = 0; + + const char* octEnd = cur + 3; + octEnd = (octEnd > end) ? end : octEnd; + + for (; cur < octEnd; ++cur) + { + const char d = *cur; + if (d >= '0' && d <= '7') + { + value = (value << 3) | (d - '0'); + } + } + out.appendChar(char(value)); + + start = cur; + break; + } + case 'x': + { + uint32_t value = 0; + for (++cur; cur < end && CharUtil::isHexDigit(*cur); ++cur) + { + value = value << 4 | _getHexDigit(*cur); + } + + // It's arguable what is appropriate. We only decode/encode 4, which the current spec has, + // but 6 are possible, so lets go large. + const Index maxUtf8EncodeCount = 6; + + char* chars = out.prepareForAppend(maxUtf8EncodeCount); + + int numChars = EncodeUnicodePointToUTF8(chars, int(value)); + out.appendInPlace(chars, numChars); + + start = cur; + break; + } + default: + { + return SLANG_FAIL; + } + } + } + } + + if (start < end) + { + out.append(start, end); + } + + return SLANG_OK; +} + +SlangResult CppStringEscapeHandler::lexQuoted(const char* cursor, const char** outCursor) +{ + *outCursor = cursor; + + if (*cursor != m_quoteChar) + { + return SLANG_FAIL; + } + cursor++; + + for (;;) + { + const char c = *cursor; + if (c == m_quoteChar) + { + *outCursor = cursor + 1; + return SLANG_OK; + } + switch (c) + { + case 0: + case '\n': + case '\r': + { + // Didn't hit closing quote! + return SLANG_FAIL; + } + case '\\': + { + ++cursor; + // Need to handle various escape sequence cases + switch (*cursor) + { + case '\'': + case '\"': + case '\\': + case '?': + case 'a': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + case 'v': + { + ++cursor; + break; + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + { + // octal escape: up to 3 characters + ++cursor; + for (int ii = 0; ii < 3; ++ii) + { + const char d = *cursor; + if (('0' <= d) && (d <= '7')) + { + ++cursor; + continue; + } + else + { + break; + } + } + break; + } + case 'x': + { + // hexadecimal escape: any number of characters + ++cursor; + for (; CharUtil::isHexDigit(*cursor); ++cursor); + + // TODO: Unicode escape sequences + break; + } + } + break; + } + default: + { + ++cursor; + break; + } + } + } +} + +// !!!!!!!!!!!!!!!!!!!!!!!!!! StringEscapeUtil !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +static CppStringEscapeHandler g_cppHandler; +static SpaceStringEscapeHandler g_spaceHandler; + +StringEscapeUtil::Handler* StringEscapeUtil::getHandler(Style style) +{ + switch (style) + { + case Style::Cpp: return &g_cppHandler; + case Style::Space: return &g_spaceHandler; + default: return nullptr; + } +} + +/* static */void StringEscapeUtil::appendQuoted(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out) +{ + const char quoteChar = handler->getQuoteChar(); + out.appendChar(quoteChar); + handler->appendEscaped(slice, out); + out.appendChar(quoteChar); +} + +/* static */SlangResult StringEscapeUtil::appendUnquoted(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out) +{ + const Index len = slice.getLength(); + + const char quoteChar = handler->getQuoteChar(); + SLANG_UNUSED(quoteChar); + + // Must have quote characters around if + SLANG_ASSERT(len >= 2 && slice[0] == quoteChar && slice[len - 1] == quoteChar); + + return handler->appendUnescaped(slice.subString(1, len - 2), out); +} + +/* static */void StringEscapeUtil::appendMaybeQuoted(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out) +{ + if (handler->isQuotingNeeded(slice)) + { + appendQuoted(handler, slice, out); + } + else + { + out.append(slice); + } +} + +/* static */SlangResult StringEscapeUtil::appendMaybeUnquoted(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out) +{ + const char quoteChar = handler->getQuoteChar(); + + const Index len = slice.getLength(); + + if (len >= 2 && slice[0] == quoteChar && slice[len - 1] == quoteChar) + { + return appendUnquoted(handler, slice, out); + } + else + { + out.append(slice); + return SLANG_OK; + } +} + + +/* static */SlangResult StringEscapeUtil::unescapeShellLike(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out) +{ + StringBuilder buf; + const char quoteChar = handler->getQuoteChar(); + + UnownedStringSlice remaining(slice); + + while (remaining.getLength()) + { + const Index index = remaining.indexOf(quoteChar); + + if (index < 0) + { + out.append(remaining); + return SLANG_OK; + } + + // Append the bit before + out.append(remaining.head(index)); + + // Okay we need to lex to the end + + const char* quotedEnd = nullptr; + SLANG_RETURN_ON_FAIL(handler->lexQuoted(remaining.begin() + index, "edEnd)); + + // Unescape it + SLANG_RETURN_ON_FAIL(appendUnquoted(handler, UnownedStringSlice(remaining.begin() + index, quotedEnd), out)); + + // Fix up remaining + remaining = UnownedStringSlice(quotedEnd, remaining.end()); + } + + return SLANG_OK; +} + +} // namespace Slang diff --git a/source/core/slang-string-escape-util.h b/source/core/slang-string-escape-util.h new file mode 100644 index 000000000..31f781dc6 --- /dev/null +++ b/source/core/slang-string-escape-util.h @@ -0,0 +1,77 @@ +#ifndef SLANG_CORE_STRING_ESCAPE_UTIL_H +#define SLANG_CORE_STRING_ESCAPE_UTIL_H + +#include "slang-string.h" + +namespace Slang { + +class StringEscapeHandler +{ +public: + + /// True if quoting is needed + virtual bool isQuotingNeeded(const UnownedStringSlice& slice) = 0; + /// True if any escaping is needed. If not slice can be used (assuming appropriate quoting) as is + virtual bool isEscapingNeeded(const UnownedStringSlice& slice) = 0; + /// Takes slice and adds any appropriate escaping (for example C++/C type escaping for special characters like '\', '"' and if not ascii will write out as hex sequence) + /// Does not append quotes + virtual SlangResult appendEscaped(const UnownedStringSlice& slice, StringBuilder& out) = 0; + /// Given a slice append it unescaped + /// Does not consume surrounding quotes + virtual SlangResult appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out) = 0; + + /// Lex quoted text. + /// The first character of cursor should be the quoteCharacter. + /// cursor points to the string to be lexed - must typically be 0 terminated. + /// outCursor on successful lex will be at the next character after was processed. + virtual SlangResult lexQuoted(const char* cursor, const char** outCursor) = 0; + + SLANG_FORCE_INLINE char getQuoteChar() const { return m_quoteChar; } + + StringEscapeHandler(char quoteChar): + m_quoteChar(quoteChar) + { + } + +protected: + const char m_quoteChar; +}; + +/* A set of function that can be used for escaping/unescaping quoting/unquoting strings. + +The distinction between 'escaping' and 'quoting' here, is just that escaping is the 'payload' of quotes. +In *principal* the Style can determine different styles of escaping that can be used. +*/ +struct StringEscapeUtil +{ + typedef StringEscapeHandler Handler; + + enum class Style + { + Cpp, ///< Cpp style quoting and escape handling + Space, ///< Applies quotes if there are spaces. Does not escape. + }; + + /// Given a style returns a handler + static Handler* getHandler(Style style); + + /// If quoting is needed appends to out quoted + static void appendMaybeQuoted(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out); + + /// If the slice appears to be quoted for the style, unquote it, else just append to out + static SlangResult appendMaybeUnquoted(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out); + + /// Appends to out slice without quotes + static SlangResult appendUnquoted(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out); + + /// Append with quotes (even if not needed) + static void appendQuoted(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out); + + /// Shells can have multiple quoted sections. This function makes a string with out quoting + static SlangResult unescapeShellLike(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out); +}; + + +} // namespace Slang + +#endif // SLANG_CORE_STRING_ESCAPE_UTIL_H diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp index 8108bdc98..cddee4bc4 100644 --- a/source/core/slang-string-util.cpp +++ b/source/core/slang-string-util.cpp @@ -2,6 +2,9 @@ #include "slang-blob.h" +#include "slang-char-util.h" +#include "slang-text-io.h" + namespace Slang { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! StringUtil !!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -491,76 +494,4 @@ SLANG_FORCE_INLINE static bool _isDigit(char c) return SLANG_OK; } -static char _getHexChar(int v) -{ - return (v <= 9) ? char(v + '0') : char(v - 10 + 'A'); -} - -static char _getEscapedChar(char c) -{ - switch (c) - { - case '\b': return 'b'; - case '\f': return 'f'; - case '\n': return 'n'; - case '\r': return 'r'; - case '\a': return 'a'; - case '\t': return 't'; - case '\v': return 'v'; - case '\'': return '\''; - case '\"': return '"'; - case '\\': return '\\'; - default: return 0; - } -} - -/* static */void StringUtil::appendEscaped(const UnownedStringSlice& slice, StringBuilder& out) -{ - const char* start = slice.begin(); - const char* cur = start; - const char*const end = slice.end(); - - for (; cur < end; ++cur) - { - const char c = *cur; - const char escapedChar = _getEscapedChar(c); - - if (escapedChar) - { - // Flush - if (start < cur) - { - out.append(start, end); - } - out.appendChar('\\'); - out.appendChar(escapedChar); - - start = cur + 1; - } - else if ( c < ' ' || c > 126) - { - // Flush - if (start < cur) - { - out.append(start, end); - } - - char buf[5] = "\\0x0"; - - buf[3] = _getHexChar((int(c) >> 4) & 0xf); - buf[4] = _getHexChar(c & 0xf); - - out.append(buf, buf + 4); - - start = cur + 1; - } - } - - if (start < end) - { - out.append(start, end); - } -} - - } // namespace Slang diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h index 8031f5c8c..5b375a09e 100644 --- a/source/core/slang-string-util.h +++ b/source/core/slang-string-util.h @@ -99,10 +99,6 @@ struct StringUtil /// Convert in to int. Returns SLANG_FAIL on error static SlangResult parseInt(const UnownedStringSlice& in, Int& outValue); - - /// Takes slice and adds C++/C type escaping for special characters (like '\', '"' and if not ascii will write out as hex sequence) - /// Does not append double quotes around the output - static void appendEscaped(const UnownedStringSlice& slice, StringBuilder& out); }; /* A helper class that allows parsing of lines from text with iteration. Uses StringUtil::extractLine for the actual underlying implementation. */ diff --git a/source/core/unix/slang-unix-process-util.cpp b/source/core/unix/slang-unix-process-util.cpp index 3f052b09d..a3b66af38 100644 --- a/source/core/unix/slang-unix-process-util.cpp +++ b/source/core/unix/slang-unix-process-util.cpp @@ -3,6 +3,7 @@ #include "../slang-common.h" #include "../slang-string-util.h" +#include "../slang-string-escape-util.h" #include <stdio.h> #include <stdlib.h> @@ -29,52 +30,25 @@ namespace Slang { #endif } -/* static */void ProcessUtil::appendCommandLineEscaped(const UnownedStringSlice& slice, StringBuilder& out) +/* static */StringEscapeHandler* ProcessUtil::getEscapeHandler() { - // TODO(JS): This escaping is not complete... ! - if (slice.indexOf(' ') >= 0 || slice.indexOf('"') >= 0) - { - out << "\""; - - const char* cur = slice.begin(); - const char* end = slice.end(); - - while (cur < end) - { - char c = *cur++; - switch (c) - { - case '\"': - { - // Escape quotes. - out << "\\\""; - break; - } - default: - out.append(c); - } - } - - out << "\""; - - return; - } - - out << slice; + return StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space); } /* static */String ProcessUtil::getCommandLineString(const CommandLine& commandLine) { + auto escapeHandler = getEscapeHandler(); + // When outputting the command line we potentially need to escape the path to the // command and args - that aren't already explicitly marked as escaped. StringBuilder cmd; - appendCommandLineEscaped(commandLine.m_executable.getUnownedSlice(), cmd); + StringEscapeUtil::appendMaybeQuoted(escapeHandler, commandLine.m_executable.getUnownedSlice(), cmd); for (const auto& arg : commandLine.m_args) { cmd << " "; if (arg.type == CommandLine::ArgType::Unescaped) { - appendCommandLineEscaped(arg.value.getUnownedSlice(), cmd); + StringEscapeUtil::appendMaybeQuoted(escapeHandler, arg.value.getUnownedSlice(), cmd); } else { @@ -260,7 +234,6 @@ namespace Slang { return SLANG_FAIL; } - /* static */uint64_t ProcessUtil::getClockFrequency() { return 1000000000; diff --git a/source/core/windows/slang-win-process-util.cpp b/source/core/windows/slang-win-process-util.cpp index 3a5a01cb3..9cc567318 100644 --- a/source/core/windows/slang-win-process-util.cpp +++ b/source/core/windows/slang-win-process-util.cpp @@ -2,6 +2,7 @@ #include "../slang-process-util.h" #include "../slang-string.h" +#include "../slang-string-escape-util.h" #ifdef _WIN32 // Include Windows header in a way that minimized namespace pollution. @@ -148,58 +149,29 @@ static DWORD WINAPI _readerThreadProc(LPVOID threadParam) return 0; } - -/* static */UnownedStringSlice ProcessUtil::getExecutableSuffix() +/* static */StringEscapeHandler* ProcessUtil::getEscapeHandler() { - return UnownedStringSlice::fromLiteral(".exe"); + return StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space); } -/* static */void ProcessUtil::appendCommandLineEscaped(const UnownedStringSlice& slice, StringBuilder& out) +/* static */UnownedStringSlice ProcessUtil::getExecutableSuffix() { - // TODO(JS): This escaping is not complete... ! - - if ((slice.indexOf(' ') >= 0 || slice.indexOf('"') >= 0)) - { - out << "\""; - - const char* cur = slice.begin(); - const char* end = slice.end(); - - while (cur < end) - { - char c = *cur++; - switch (c) - { - case '\"': - { - // Escape quotes. - out << "\\\""; - break; - } - default: - out.append(c); - } - } - - out << "\""; - return; - } - else - { - out << slice; - } + return UnownedStringSlice::fromLiteral(".exe"); } /* static */String ProcessUtil::getCommandLineString(const CommandLine& commandLine) { + auto escapeHandler = getEscapeHandler(); + StringBuilder cmd; - appendCommandLineEscaped(commandLine.m_executable.getUnownedSlice(), cmd); + StringEscapeUtil::appendMaybeQuoted(escapeHandler, commandLine.m_executable.getUnownedSlice(), cmd); + for (const auto& arg : commandLine.m_args) { cmd << " "; if (arg.type == CommandLine::ArgType::Unescaped) { - appendCommandLineEscaped(arg.value.getUnownedSlice(), cmd); + StringEscapeUtil::appendMaybeQuoted(escapeHandler, arg.value.getUnownedSlice(), cmd); } else { @@ -269,7 +241,7 @@ static DWORD WINAPI _readerThreadProc(LPVOID threadParam) if (commandLine.m_executableType == CommandLine::ExecutableType::Path) { StringBuilder cmd; - appendCommandLineEscaped(commandLine.m_executable.getUnownedSlice(), cmd); + StringEscapeUtil::appendMaybeQuoted(getEscapeHandler(), commandLine.m_executable.getUnownedSlice(), cmd); pathBuffer = cmd.toWString(); path = pathBuffer.begin(); diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index 1cb222294..e0ad2163a 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -890,7 +890,7 @@ struct OptionsParser SLANG_RETURN_ON_FAIL(tryReadCommandLineArgumentRaw(sink, arg, &argCursor, argEnd, &includeDirStr)); } - compileRequest->addSearchPath(String(includeDirStr).begin()); + compileRequest->addSearchPath(includeDirStr); } // // A `-o` option is used to specify a desired output file. diff --git a/source/slang/slang-preprocessor.cpp b/source/slang/slang-preprocessor.cpp index bc78e4d31..7bee5afd2 100644 --- a/source/slang/slang-preprocessor.cpp +++ b/source/slang/slang-preprocessor.cpp @@ -1051,9 +1051,8 @@ static void MaybeBeginMacroExpansion( // We need to escape to a string newToken.type = TokenType::StringLiteral; - buf.appendChar('"'); - StringUtil::appendEscaped(humaneSourceLoc.pathInfo.foundPath.getUnownedSlice(), buf); - buf.appendChar('"'); + auto escapeHandler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::Cpp); + StringEscapeUtil::appendQuoted(escapeHandler, humaneSourceLoc.pathInfo.foundPath.getUnownedSlice(), buf); } // We are going to keep the actual text in the slice pool, so it stays in scope diff --git a/tests/compute/transcendental-double.slang b/tests/compute/transcendental-double.slang new file mode 100644 index 000000000..19ea417d8 --- /dev/null +++ b/tests/compute/transcendental-double.slang @@ -0,0 +1,28 @@ +//TEST(compute):COMPARE_COMPUTE:-cuda -output-using-type -shaderobj +//TEST(compute):COMPARE_COMPUTE:-cpu -output-using-type -shaderobj +//TEST(compute):COMPARE_COMPUTE: -output-using-type -shaderobj + +// Cos values are all 0 on D3d12(!) +//DISABLE_TEST(compute):COMPARE_COMPUTE: -dx12 -output-using-type -shaderobj +// When using double on vulkan the values are incorrect(!) +//DISABLE_TEST(compute,vulkan):COMPARE_COMPUTE:-vk -output-using-type -shaderobj + +//TEST_INPUT:ubuffer(data=[0 0 0 0 0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer<float> outputBuffer : register(u0); + +float quantize(double value) +{ + return int(value * 256) * 1.0f / 256.0f; +} + +[numthreads(4, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + float values[] = { -9, 9, -3, 3 }; + + int tid = int(dispatchThreadID.x); + float value = values[tid]; + + outputBuffer[tid * 2 + 0] = quantize(sin(double(value))); + outputBuffer[tid * 2 + 1] = quantize(cos(double(value))); +} diff --git a/tests/compute/transcendental-double.slang.expected.txt b/tests/compute/transcendental-double.slang.expected.txt new file mode 100644 index 000000000..ceef37fce --- /dev/null +++ b/tests/compute/transcendental-double.slang.expected.txt @@ -0,0 +1,9 @@ +type: float +-0.410156 +-0.910156 +0.410156 +-0.910156 +-0.140625 +-0.988281 +0.140625 +-0.988281 diff --git a/tests/compute/transcendental.slang b/tests/compute/transcendental.slang index e9feb4d95..cf8548939 100644 --- a/tests/compute/transcendental.slang +++ b/tests/compute/transcendental.slang @@ -1,19 +1,14 @@ -//TEST(compute):COMPARE_COMPUTE:-cuda -shaderobj -//TEST(compute):COMPARE_COMPUTE:-cpu -shaderobj -//TEST(compute):COMPARE_COMPUTE: -shaderobj -//TEST(compute,vulcan):COMPARE_COMPUTE:-vk -shaderobj +//TEST(compute):COMPARE_COMPUTE:-cuda -output-using-type -shaderobj +//TEST(compute):COMPARE_COMPUTE:-cpu -output-using-type -shaderobj +//TEST(compute):COMPARE_COMPUTE: -output-using-type -shaderobj +//TEST(compute,vulkan):COMPARE_COMPUTE:-vk -output-using-type -shaderobj -//TEST_INPUT:ubuffer(data=[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], stride=4):out,name=outputBuffer -RWStructuredBuffer<int> outputBuffer : register(u0); +//TEST_INPUT:ubuffer(data=[0 0 0 0 0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer<float> outputBuffer : register(u0); -int quantize(double value) +float quantize(float value) { - return int(value * 256); -} - -int quantize(float value) -{ - return int(value * 256); + return int(value * 256) * 1.0f / 256.0f; } [numthreads(4, 1, 1)] @@ -24,9 +19,6 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) int tid = int(dispatchThreadID.x); float value = values[tid]; - outputBuffer[tid * 4] = quantize(sin(value)); - outputBuffer[tid * 4 + 1] = quantize(cos(value)); - - outputBuffer[tid * 4 + 2] = quantize(sin(double(value))); - outputBuffer[tid * 4 + 3] = quantize(cos(double(value))); + outputBuffer[tid * 2] = quantize(sin(value)); + outputBuffer[tid * 2 + 1] = quantize(cos(value)); }
\ No newline at end of file diff --git a/tests/compute/transcendental.slang.expected.txt b/tests/compute/transcendental.slang.expected.txt index 4a525cc7c..ceef37fce 100644 --- a/tests/compute/transcendental.slang.expected.txt +++ b/tests/compute/transcendental.slang.expected.txt @@ -1,16 +1,9 @@ -FFFFFF97 -FFFFFF17 -FFFFFF97 -FFFFFF17 -69 -FFFFFF17 -69 -FFFFFF17 -FFFFFFDC -FFFFFF03 -FFFFFFDC -FFFFFF03 -24 -FFFFFF03 -24 -FFFFFF03 +type: float +-0.410156 +-0.910156 +0.410156 +-0.910156 +-0.140625 +-0.988281 +0.140625 +-0.988281 diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp index cac852b51..3a9b15648 100644 --- a/tools/slang-test/slang-test-main.cpp +++ b/tools/slang-test/slang-test-main.cpp @@ -5,15 +5,19 @@ #include "../../source/core/slang-std-writers.h" #include "../../source/core/slang-hex-dump-util.h" #include "../../source/core/slang-type-text-util.h" +#include "../../source/core/slang-memory-arena.h" #include "../../slang-com-helper.h" #include "../../source/core/slang-string-util.h" +#include "../../source/core/slang-string-escape-util.h" + #include "../../source/core/slang-byte-encode-util.h" #include "../../source/core/slang-char-util.h" #include "../../source/core/slang-process-util.h" #include "../../source/core/slang-render-api-util.h" + #include "directory-util.h" #include "test-context.h" #include "test-reporter.h" @@ -206,61 +210,58 @@ String collectRestOfLine(char const** ioCursor) return getString(textBegin, textEnd); } +static bool _isEndOfCategoryList(char c) +{ + switch (c) + { + case '\n': + case '\r': + case 0: + case ')': + { + return true; + } + default: return false; + } +} + static SlangResult _parseCategories(TestCategorySet* categorySet, char const** ioCursor, TestOptions& out) { char const* cursor = *ioCursor; - // Right after the `TEST` keyword, the user may specify - // one or more categories for the test. + // If don't have ( we don't have category list if (*cursor == '(') { cursor++; - // optional test category - skipHorizontalSpace(&cursor); - char const* categoryStart = cursor; - for (;;) - { - switch (*cursor) - { - default: - { - cursor++; - continue; - } - case ',': - case ')': - { - char const* categoryEnd = cursor; - cursor++; + const char*const start = cursor; - auto categoryName = getString(categoryStart, categoryEnd); - TestCategory* category = categorySet->find(categoryName); + // Find the end + for (; !_isEndOfCategoryList(*cursor); ++cursor); + if (*cursor != ')') + { + *ioCursor = cursor; + return SLANG_FAIL; + } + cursor++; - if (!category) - { - // Failure if we don't find the category - return SLANG_FAIL; - } + List<UnownedStringSlice> slices; + StringUtil::split(UnownedStringSlice(start, cursor - 1), ',', slices); - out.addCategory(category); + for (auto& slice : slices) + { + // Trim any whitespace + auto categoryName = slice.trim(); - if (*categoryEnd == ',') - { - skipHorizontalSpace(&cursor); - categoryStart = cursor; - continue; - } + TestCategory* category = categorySet->find(categoryName); - *ioCursor = cursor; - return SLANG_OK; - } - case 0: case '\r': case '\n': - { - return SLANG_FAIL; - } + if (!category) + { + // Mark this test as disabled, as we don't have all of the categories + out.isEnabled = false; + break; } - break; + out.addCategory(category); } } @@ -268,26 +269,60 @@ static SlangResult _parseCategories(TestCategorySet* categorySet, char const** i return SLANG_OK; } +static SlangResult _parseArg(const char** ioCursor, UnownedStringSlice& outArg) +{ + const char* cursor = *ioCursor; + const char*const argBegin = cursor; + + // Let's try to read one option + for (;;) + { + switch (*cursor) + { + default: + { + ++cursor; + break; + } + case '"': + { + // If we have quotes let's just parse them as is and make output + auto escapeHandler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space); + SLANG_RETURN_ON_FAIL(escapeHandler->lexQuoted(cursor, &cursor)); + break; + } + case 0: + case '\r': + case '\n': + case ' ': + case '\t': + { + char const* argEnd = cursor; + assert(argBegin != argEnd); + + outArg = UnownedStringSlice(argBegin, argEnd); + *ioCursor = cursor; + return SLANG_OK; + } + } + } +} -static TestResult _gatherTestOptions( +static SlangResult _gatherTestOptions( TestCategorySet* categorySet, char const** ioCursor, TestOptions& outOptions) { - if (SLANG_FAILED(_parseCategories(categorySet, ioCursor, outOptions))) - { - return TestResult::Fail; - } + SLANG_RETURN_ON_FAIL(_parseCategories(categorySet, ioCursor, outOptions)); char const* cursor = *ioCursor; - if(*cursor == ':') - cursor++; - else + if(*cursor != ':') { - return TestResult::Fail; + return SLANG_FAIL; } - + cursor++; + // Next scan for a sub-command name char const* commandStart = cursor; for(;;) @@ -302,7 +337,7 @@ static TestResult _gatherTestOptions( break; case 0: case '\r': case '\n': - return TestResult::Fail; + return SLANG_FAIL; } break; @@ -315,12 +350,11 @@ static TestResult _gatherTestOptions( cursor++; else { - return TestResult::Fail; + return SLANG_FAIL; } // Now scan for arguments. For now we just assume that // any whitespace separation indicates a new argument - // (we don't support quoting) for(;;) { skipHorizontalSpace(&cursor); @@ -332,32 +366,17 @@ static TestResult _gatherTestOptions( skipToEndOfLine(&cursor); *ioCursor = cursor; - return TestResult::Pass; + return SLANG_OK; default: break; } // Let's try to read one option - char const* argBegin = cursor; - for(;;) - { - switch( *cursor ) - { - default: - cursor++; - continue; - - case 0: case '\r': case '\n': case ' ': case '\t': - break; - } - - break; - } - char const* argEnd = cursor; - assert(argBegin != argEnd); + UnownedStringSlice arg; + SLANG_RETURN_ON_FAIL(_parseArg(&cursor, arg)); - outOptions.args.add(getString(argBegin, argEnd)); + outOptions.args.add(arg); } } @@ -379,12 +398,40 @@ static void _combineOptions( } } +static SlangResult _extractCommand(const char** ioCursor, UnownedStringSlice& outCommand) +{ + const char* cursor = *ioCursor; + const char*const start = cursor; + + while (true) + { + const char c = *cursor; + + if (CharUtil::isAlpha(c) || c == '_') + { + cursor++; + continue; + } + + if (c == ':' || c == '(' || c == 0 || c == '\n' || c == '\r') + { + *ioCursor = cursor; + outCommand = UnownedStringSlice(start, cursor); + return SLANG_OK; + } + + return SLANG_FAIL; + } +} + // Try to read command-line options from the test file itself -TestResult gatherTestsForFile( +static SlangResult _gatherTestsForFile( TestCategorySet* categorySet, String filePath, - FileTestList* testList) + FileTestList* outTestList) { + outTestList->tests.clear(); + String fileContents; try { @@ -392,7 +439,7 @@ TestResult gatherTestsForFile( } catch (const Slang::IOException&) { - return TestResult::Fail; + return SLANG_FAIL; } // Walk through the lines of the file, looking for test commands @@ -413,30 +460,48 @@ TestResult gatherTestsForFile( continue; } - // Look for a pattern that matches what we want - if (match(&cursor, "TEST_IGNORE_FILE")) + UnownedStringSlice command; + + if (SLANG_FAILED(_extractCommand(&cursor, command))) { - return TestResult::Ignored; + // Couldn't find a command so skip + skipToEndOfLine(&cursor); + continue; } - TestDetails testDetails; - if (match(&cursor, "DISABLE_")) + // Look for a pattern that matches what we want + if (command == "TEST_IGNORE_FILE") { - testDetails.options.isEnabled = false; + outTestList->tests.clear(); + return SLANG_OK; } - if (match(&cursor, "TEST_CATEGORY")) + const UnownedStringSlice disablePrefix = UnownedStringSlice::fromLiteral("DISABLE_"); + + TestDetails testDetails; + { - if (SLANG_FAILED(_parseCategories(categorySet, &cursor, fileOptions))) + if (command.startsWith(disablePrefix)) { - return TestResult::Fail; + testDetails.options.isEnabled = false; + command = command.tail(disablePrefix.getLength()); } } - if(match(&cursor, "TEST")) - { - if(_gatherTestOptions(categorySet, &cursor, testDetails.options) != TestResult::Pass) - return TestResult::Fail; + if (command == "TEST_CATEGORY") + { + SlangResult res = _parseCategories(categorySet, &cursor, fileOptions); + + // If if failed we are done, unless it was just 'not available' + if (SLANG_FAILED(res) && res != SLANG_E_NOT_AVAILABLE) return res; + + skipToEndOfLine(&cursor); + continue; + } + + if(command == "TEST") + { + SLANG_RETURN_ON_FAIL(_gatherTestOptions(categorySet, &cursor, testDetails.options)); // See if the type of test needs certain APIs available const RenderApiFlags testRequiredApis = _getRequiredRenderApisByCommand(testDetails.options.command.getUnownedSlice()); @@ -445,27 +510,28 @@ TestResult gatherTestsForFile( // Apply the file wide options _combineOptions(categorySet, fileOptions, testDetails.options); - testList->tests.add(testDetails); + outTestList->tests.add(testDetails); } - else if (match(&cursor, "DIAGNOSTIC_TEST")) + else if (command == "DIAGNOSTIC_TEST") { - if (_gatherTestOptions(categorySet, &cursor, testDetails.options) != TestResult::Pass) - return TestResult::Fail; + SLANG_RETURN_ON_FAIL(_gatherTestOptions(categorySet, &cursor, testDetails.options)); // Apply the file wide options _combineOptions(categorySet, fileOptions, testDetails.options); // Mark that it is a diagnostic test testDetails.options.type = TestOptions::Type::Diagnostic; - testList->tests.add(testDetails); + outTestList->tests.add(testDetails); } else { + // Hmm we don't know what kind of test this actually is. + // Assume that's ok and this *isn't* a test and ignore. skipToEndOfLine(&cursor); } } - return TestResult::Pass; + return SLANG_OK; } Result spawnAndWaitExe(TestContext* context, const String& testPath, const CommandLine& cmdLine, ExecuteResult& outRes) @@ -487,6 +553,24 @@ Result spawnAndWaitExe(TestContext* context, const String& testPath, const Comma return res; } +static const char* _getUnescaped(StringEscapeHandler* handler, const CommandLine::Arg& arg, MemoryArena& arena) +{ + if (arg.type == CommandLine::ArgType::Escaped) + { + StringBuilder buf; + StringEscapeUtil::unescapeShellLike(handler, arg.value.getUnownedSlice(), buf); + + // We strictly only need to allocate if the result is different. + // That an arg marked as 'escaped' does not mean it produces a different result when decoding. + if (buf != arg.value) + { + return arena.allocateString(buf.getBuffer(), buf.getLength()); + } + } + + return arg.value.getBuffer(); +} + Result spawnAndWaitSharedLibrary(TestContext* context, const String& testPath, const CommandLine& cmdLine, ExecuteResult& outRes) { const auto& options = context->options; @@ -534,11 +618,19 @@ Result spawnAndWaitSharedLibrary(TestContext* context, const String& testPath, c String exePath = Path::combine(context->exeDirectoryPath, exeName); + + // Use the arena to hold any unescaped strings + MemoryArena arena(1024); List<const char*> args; + args.add(exePath.getBuffer()); - for (Index i = 0; i < cmdLine.m_args.getCount(); ++i) + { - args.add(cmdLine.m_args[i].value.getBuffer()); + auto escapeHandler = ProcessUtil::getEscapeHandler(); + for (Index i = 0; i < cmdLine.m_args.getCount(); ++i) + { + args.add(_getUnescaped(escapeHandler, cmdLine.m_args[i], arena)); + } } SlangResult res = func(&stdWriters, context->getSession(), int(args.getCount()), args.begin()); @@ -1348,7 +1440,15 @@ TestResult runCompile(TestContext* context, TestInput& input) for (auto arg : input.testOptions->args) { - cmdLine.addArg(arg); + // If there is a quote in the string, assume it is 'escaped'. + if (arg.indexOf('"') >= 0) + { + cmdLine.addEscapedArg(arg); + } + else + { + cmdLine.addArg(arg); + } } ExecuteResult exeRes; @@ -3040,24 +3140,26 @@ static bool _canIgnore(TestContext* context, const TestDetails& details) return false; } -void runTestsOnFile( +static SlangResult _runTestsOnFile( TestContext* context, String filePath) { // Gather a list of tests to run FileTestList testList; + + SLANG_RETURN_ON_FAIL(_gatherTestsForFile(&context->categorySet, filePath, &testList)); - if( gatherTestsForFile(&context->categorySet, filePath, &testList) == TestResult::Ignored ) + if (testList.tests.getCount() == 0) { // Test was explicitly ignored - return; + return SLANG_OK; } // Note cases where a test file exists, but we found nothing to run if( testList.tests.getCount() == 0 ) { context->reporter->addTest(filePath, TestResult::Ignored); - return; + return SLANG_OK; } RenderApiFlags apiUsedFlags = 0; @@ -3187,6 +3289,8 @@ void runTestsOnFile( // Could determine if to continue or not here... based on result } } + + return SLANG_OK; } @@ -3254,10 +3358,24 @@ void runTestsInDirectory( if( shouldRunTest(context, file) ) { // fprintf(stderr, "slang-test: found '%s'\n", file.getBuffer()); - runTestsOnFile(context, file); + if (SLANG_FAILED(_runTestsOnFile(context, file))) + { + auto reporter = context->reporter; + + { + TestReporter::TestScope scope(reporter, file); + reporter->message(TestMessageType::RunError, "slang-test: unable to parse test"); + + reporter->addResult(TestResult::Fail); + } + + // Output there was some kind of error trying to run the tests on this file + // fprintf(stderr, "slang-test: unable to parse test '%s'\n", file.getBuffer()); + } } } } + { List<String> subDirs; DirectoryUtil::findDirectories(directoryPath, subDirs); @@ -3298,11 +3416,11 @@ SlangResult innerMain(int argc, char** argv) // Set up our test categories here auto fullTestCategory = categorySet.add("full", nullptr); auto quickTestCategory = categorySet.add("quick", fullTestCategory); - /*auto smokeTestCategory = */categorySet.add("smoke", quickTestCategory); + auto smokeTestCategory = categorySet.add("smoke", quickTestCategory); auto renderTestCategory = categorySet.add("render", fullTestCategory); /*auto computeTestCategory = */categorySet.add("compute", fullTestCategory); auto vulkanTestCategory = categorySet.add("vulkan", fullTestCategory); - auto unitTestCatagory = categorySet.add("unit-test", fullTestCategory); + auto unitTestCategory = categorySet.add("unit-test", fullTestCategory); auto cudaTestCategory = categorySet.add("cuda", fullTestCategory); auto optixTestCategory = categorySet.add("optix", cudaTestCategory); @@ -3311,7 +3429,9 @@ SlangResult innerMain(int argc, char** argv) auto waveActiveCategory = categorySet.add("wave-active", waveTestCategory); auto compatibilityIssueCategory = categorySet.add("compatibility-issue", fullTestCategory); - + + auto sharedLibraryCategory = categorySet.add("shared-library", fullTestCategory); + #if SLANG_WINDOWS_FAMILY auto windowsCategory = categorySet.add("windows", fullTestCategory); #endif @@ -3464,7 +3584,8 @@ SlangResult innerMain(int argc, char** argv) filePath << "unit-tests/" << cur->m_name << ".internal"; TestOptions testOptions; - testOptions.categories.add(unitTestCatagory); + testOptions.categories.add(unitTestCategory); + testOptions.categories.add(smokeTestCategory); testOptions.command = filePath; if (shouldRunTest(&context, testOptions.command)) diff --git a/tools/slang-test/unit-test-path.cpp b/tools/slang-test/unit-test-path.cpp index fb1e7a0ff..98f44753e 100644 --- a/tools/slang-test/unit-test-path.cpp +++ b/tools/slang-test/unit-test-path.cpp @@ -9,6 +9,8 @@ using namespace Slang; static void pathUnitTest() { +#if SLANG_WINDOWS_FAMILY + // Disable for now on non windows has some problems on *some* Linux based CI. { String path; SlangResult res = Path::getCanonical("source/slang", path); @@ -21,6 +23,7 @@ static void pathUnitTest() String parentPath2 = Path::getParentDirectory(path); SLANG_CHECK(parentPath == parentPath2); } +#endif // Test the paths { SLANG_CHECK(Path::simplify(".") == "."); |
