summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/visual-studio/core/core.vcxproj6
-rw-r--r--build/visual-studio/core/core.vcxproj.filters14
-rw-r--r--build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj1
-rw-r--r--build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters3
-rw-r--r--source/compiler-core/slang-command-line-args.cpp2
-rw-r--r--source/compiler-core/slang-gcc-compiler-util.cpp2
-rw-r--r--source/core/slang-command-line.cpp54
-rw-r--r--source/core/slang-command-line.h60
-rw-r--r--source/core/slang-process-util.cpp78
-rw-r--r--source/core/slang-process-util.h79
-rw-r--r--source/core/slang-process.h73
-rw-r--r--source/core/slang-stream.cpp276
-rw-r--r--source/core/slang-stream.h61
-rw-r--r--source/core/slang-string-util.cpp54
-rw-r--r--source/core/slang-string-util.h3
-rw-r--r--source/core/unix/slang-unix-process-util.cpp246
-rw-r--r--source/core/unix/slang-unix-process.cpp430
-rw-r--r--source/core/windows/slang-win-process-util.cpp333
-rw-r--r--source/core/windows/slang-win-process.cpp495
-rw-r--r--source/slang/slang-repro.cpp4
-rw-r--r--tools/render-test/render-test-main.cpp6
-rw-r--r--tools/slang-test/slang-test-main.cpp31
-rw-r--r--tools/slang-unit-test/unit-test-process.cpp125
-rw-r--r--tools/test-proxy/test-proxy-main.cpp113
-rw-r--r--tools/unit-test/slang-unit-test.h1
25 files changed, 1876 insertions, 674 deletions
diff --git a/build/visual-studio/core/core.vcxproj b/build/visual-studio/core/core.vcxproj
index 777bf4dd3..2b54e3785 100644
--- a/build/visual-studio/core/core.vcxproj
+++ b/build/visual-studio/core/core.vcxproj
@@ -285,6 +285,7 @@
<ClInclude Include="..\..\..\source\core\slang-char-util.h" />
<ClInclude Include="..\..\..\source\core\slang-chunked-list.h" />
<ClInclude Include="..\..\..\source\core\slang-com-object.h" />
+ <ClInclude Include="..\..\..\source\core\slang-command-line.h" />
<ClInclude Include="..\..\..\source\core\slang-common.h" />
<ClInclude Include="..\..\..\source\core\slang-compression-system.h" />
<ClInclude Include="..\..\..\source\core\slang-deflate-compression-system.h" />
@@ -304,6 +305,7 @@
<ClInclude Include="..\..\..\source\core\slang-offset-container.h" />
<ClInclude Include="..\..\..\source\core\slang-platform.h" />
<ClInclude Include="..\..\..\source\core\slang-process-util.h" />
+ <ClInclude Include="..\..\..\source\core\slang-process.h" />
<ClInclude Include="..\..\..\source\core\slang-random-generator.h" />
<ClInclude Include="..\..\..\source\core\slang-render-api-util.h" />
<ClInclude Include="..\..\..\source\core\slang-riff-file-system.h" />
@@ -337,6 +339,7 @@
<ClCompile Include="..\..\..\source\core\slang-byte-encode-util.cpp" />
<ClCompile Include="..\..\..\source\core\slang-char-encode.cpp" />
<ClCompile Include="..\..\..\source\core\slang-char-util.cpp" />
+ <ClCompile Include="..\..\..\source\core\slang-command-line.cpp" />
<ClCompile Include="..\..\..\source\core\slang-deflate-compression-system.cpp" />
<ClCompile Include="..\..\..\source\core\slang-file-system.cpp" />
<ClCompile Include="..\..\..\source\core\slang-free-list.cpp" />
@@ -346,6 +349,7 @@
<ClCompile Include="..\..\..\source\core\slang-memory-arena.cpp" />
<ClCompile Include="..\..\..\source\core\slang-offset-container.cpp" />
<ClCompile Include="..\..\..\source\core\slang-platform.cpp" />
+ <ClCompile Include="..\..\..\source\core\slang-process-util.cpp" />
<ClCompile Include="..\..\..\source\core\slang-random-generator.cpp" />
<ClCompile Include="..\..\..\source\core\slang-render-api-util.cpp" />
<ClCompile Include="..\..\..\source\core\slang-riff-file-system.cpp" />
@@ -367,7 +371,7 @@
<ClCompile Include="..\..\..\source\core\slang-uint-set.cpp" />
<ClCompile Include="..\..\..\source\core\slang-writer.cpp" />
<ClCompile Include="..\..\..\source\core\slang-zip-file-system.cpp" />
- <ClCompile Include="..\..\..\source\core\windows\slang-win-process-util.cpp" />
+ <ClCompile Include="..\..\..\source\core\windows\slang-win-process.cpp" />
</ItemGroup>
<ItemGroup>
<Natvis Include="..\..\..\source\core\core.natvis" />
diff --git a/build/visual-studio/core/core.vcxproj.filters b/build/visual-studio/core/core.vcxproj.filters
index 639886474..31322c04f 100644
--- a/build/visual-studio/core/core.vcxproj.filters
+++ b/build/visual-studio/core/core.vcxproj.filters
@@ -42,6 +42,9 @@
<ClInclude Include="..\..\..\source\core\slang-com-object.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\core\slang-command-line.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\core\slang-common.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -99,6 +102,9 @@
<ClInclude Include="..\..\..\source\core\slang-process-util.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\core\slang-process.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\core\slang-random-generator.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -194,6 +200,9 @@
<ClCompile Include="..\..\..\source\core\slang-char-util.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\core\slang-command-line.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\core\slang-deflate-compression-system.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -221,6 +230,9 @@
<ClCompile Include="..\..\..\source\core\slang-platform.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\core\slang-process-util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\core\slang-random-generator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -284,7 +296,7 @@
<ClCompile Include="..\..\..\source\core\slang-zip-file-system.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="..\..\..\source\core\windows\slang-win-process-util.cpp">
+ <ClCompile Include="..\..\..\source\core\windows\slang-win-process.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
diff --git a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj
index 6d0fe41e8..c8eadc484 100644
--- a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj
+++ b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj
@@ -280,6 +280,7 @@
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-json.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-memory-arena.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-path.cpp" />
+ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-process.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-riff.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-short-list.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-string.cpp" />
diff --git a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters
index 148a94a40..2578a8736 100644
--- a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters
+++ b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters
@@ -41,6 +41,9 @@
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-path.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-process.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-riff.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/source/compiler-core/slang-command-line-args.cpp b/source/compiler-core/slang-command-line-args.cpp
index ec03e3116..e34dfdead 100644
--- a/source/compiler-core/slang-command-line-args.cpp
+++ b/source/compiler-core/slang-command-line-args.cpp
@@ -17,7 +17,7 @@ void CommandLineArgs::setArgs(const char*const* args, size_t argCount)
StringBuilder buf;
- auto escapeHandler = ProcessUtil::getEscapeHandler();
+ auto escapeHandler = Process::getEscapeHandler();
for (size_t i = 0; i < argCount; ++i)
{
diff --git a/source/compiler-core/slang-gcc-compiler-util.cpp b/source/compiler-core/slang-gcc-compiler-util.cpp
index c897f5492..034f349e0 100644
--- a/source/compiler-core/slang-gcc-compiler-util.cpp
+++ b/source/compiler-core/slang-gcc-compiler-util.cpp
@@ -419,7 +419,7 @@ static SlangResult _parseGCCFamilyLine(const UnownedStringSlice& line, LineParse
case SLANG_EXECUTABLE:
{
outPath << options.modulePath;
- outPath << ProcessUtil::getExecutableSuffix();
+ outPath << Process::getExecutableSuffix();
return SLANG_OK;
}
case SLANG_OBJECT_CODE:
diff --git a/source/core/slang-command-line.cpp b/source/core/slang-command-line.cpp
new file mode 100644
index 000000000..419c031ab
--- /dev/null
+++ b/source/core/slang-command-line.cpp
@@ -0,0 +1,54 @@
+// slang-command-line.cpp
+#include "slang-command-line.h"
+
+#include "slang-process.h"
+
+#include "slang-string.h"
+#include "slang-string-escape-util.h"
+#include "slang-string-util.h"
+
+#include "../../slang-com-helper.h"
+
+namespace Slang {
+
+void CommandLine::addPrefixPathArg(const char* prefix, const String& path, const char* pathPostfix)
+{
+ StringBuilder builder;
+ builder << prefix << path;
+ if (pathPostfix)
+ {
+ // Work out the path with the postfix
+ builder << pathPostfix;
+ }
+ addArg(builder.ProduceString());
+}
+
+void CommandLine::setExecutable(const String& dir, const String& name)
+{
+ StringBuilder builder;
+ Path::combineIntoBuilder(dir.getUnownedSlice(), name.getUnownedSlice(), builder);
+ builder << Process::getExecutableSuffix();
+ setExecutablePath(builder.ProduceString());
+}
+
+void CommandLine::append(StringBuilder& out) const
+{
+ auto escapeHandler = Process::getEscapeHandler();
+
+ StringEscapeUtil::appendMaybeQuoted(escapeHandler, m_executable.getUnownedSlice(), out);
+
+ for (const auto& arg : m_args)
+ {
+ out << " ";
+ StringEscapeUtil::appendMaybeQuoted(escapeHandler, arg.getUnownedSlice(), out);
+ }
+}
+
+String CommandLine::toString() const
+{
+ StringBuilder buf;
+ append(buf);
+ return buf.ProduceString();
+}
+
+} // namespace Slang
diff --git a/source/core/slang-command-line.h b/source/core/slang-command-line.h
new file mode 100644
index 000000000..85ba1a331
--- /dev/null
+++ b/source/core/slang-command-line.h
@@ -0,0 +1,60 @@
+// slang-command-line.h
+#ifndef SLANG_COMMAND_LINE_H
+#define SLANG_COMMAND_LINE_H
+
+#include "slang-string.h"
+#include "slang-list.h"
+
+namespace Slang {
+
+struct CommandLine
+{
+ enum class ExecutableType
+ {
+ Unknown, ///< The executable is not specified
+ Path, ///< The executable is set as a path (ie won't be searched for)
+ Filename, ///< The executable is set as a filename
+ };
+
+ /// Add args - assumed unescaped
+ void addArg(const String& in) { m_args.add(in); }
+ void addArgs(const String* args, Int argsCount) { for (Int i = 0; i < argsCount; ++i) addArg(args[i]); }
+
+ /// Find the index of an arg which is exact match for slice
+ SLANG_INLINE Index findArgIndex(const UnownedStringSlice& slice) const { return m_args.indexOf(slice); }
+
+ /// Set the executable path.
+ /// NOTE! On some targets the executable path *must* include an extension to be able to start as a process
+ void setExecutablePath(const String& path) { m_executableType = ExecutableType::Path; m_executable = path; }
+
+ /// Set the executable path from a base directory and an executable name (no suffix such as '.exe' needed)
+ void setExecutable(const String& dir, const String& name);
+
+ /// Set a filename (such that the path will be looked up
+ void setExecutableFilename(const String& filename) { m_executableType = ExecutableType::Filename; m_executable = filename; }
+
+ /// For handling args where the switch is placed directly in front of the path
+ void addPrefixPathArg(const char* prefix, const String& path, const char* pathPostfix = nullptr);
+
+ /// Get the total number of args
+ SLANG_FORCE_INLINE Index getArgCount() const { return m_args.getCount(); }
+
+ /// Reset to the initial state
+ void reset() { *this = CommandLine(); }
+
+ /// Append the command line to out
+ void append(StringBuilder& out) const;
+ /// convert into a string
+ String toString() const;
+
+ /// Ctor
+ CommandLine():m_executableType(ExecutableType::Unknown) {}
+
+ ExecutableType m_executableType; ///< How the executable is specified
+ String m_executable; ///< Executable to run. Note that the executable is never escaped.
+ List<String> m_args; ///< The arguments (Stored *unescaped*)
+};
+
+}
+
+#endif // SLANG_COMMAND_LINE_H
diff --git a/source/core/slang-process-util.cpp b/source/core/slang-process-util.cpp
new file mode 100644
index 000000000..b1466d150
--- /dev/null
+++ b/source/core/slang-process-util.cpp
@@ -0,0 +1,78 @@
+// slang-process-util.cpp
+
+#include "slang-process-util.h"
+
+#include "slang-string.h"
+#include "slang-string-escape-util.h"
+#include "slang-string-util.h"
+
+#include "../../slang-com-helper.h"
+
+namespace Slang {
+
+/* static */SlangResult ProcessUtil::execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult)
+{
+ RefPtr<Process> process;
+ SLANG_RETURN_ON_FAIL(Process::create(commandLine, 0, process));
+ SLANG_RETURN_ON_FAIL(readUntilTermination(process, outExecuteResult));
+ return SLANG_OK;
+}
+
+static Index _getCount(List<Byte>* buf)
+{
+ return buf ? buf->getCount() : 0;
+}
+
+// We may want something more sophisticated here, if bytes is something other than ascii/utf8
+static String _getText(const ConstArrayView<Byte>& bytes)
+{
+ StringBuilder buf;
+ StringUtil::appendStandardLines(UnownedStringSlice((const char*)bytes.begin(), (const char*)bytes.end()), buf);
+ return buf.ProduceString();
+}
+
+/* static */SlangResult ProcessUtil::readUntilTermination(Process* process, ExecuteResult& outExecuteResult)
+{
+ List<Byte> stdOut;
+ List<Byte> stdError;
+
+ SLANG_RETURN_ON_FAIL(readUntilTermination(process, &stdOut, &stdError));
+
+ // Get the return code
+ outExecuteResult.resultCode = ExecuteResult::ResultCode(process->getReturnValue());
+
+ outExecuteResult.standardOutput = _getText(stdOut.getArrayView());
+ outExecuteResult.standardError = _getText(stdError.getArrayView());
+
+ return SLANG_OK;
+}
+
+/* static */SlangResult ProcessUtil::readUntilTermination(Process* process, List<Byte>* outStdOut, List<Byte>* outStdError)
+{
+ Stream* stdOutStream = process->getStream(Process::StreamType::StdOut);
+ Stream* stdErrorStream = process->getStream(Process::StreamType::ErrorOut);
+
+ while (!process->isTerminated())
+ {
+ const auto preCount = _getCount(outStdOut) + _getCount(outStdError);
+
+ SLANG_RETURN_ON_FAIL(StreamUtil::readOrDiscard(stdOutStream, 0, outStdOut));
+ SLANG_RETURN_ON_FAIL(StreamUtil::readOrDiscard(stdErrorStream, 0, outStdError));
+
+ const auto postCount = _getCount(outStdOut) + _getCount(outStdError);
+
+ // If nothing was read, we can yield
+ if (preCount == postCount)
+ {
+ Process::sleepCurrentThread(0);
+ }
+ }
+
+ // Read anything remaining
+ SLANG_RETURN_ON_FAIL(StreamUtil::readOrDiscardAll(stdOutStream, 0, outStdOut));
+ SLANG_RETURN_ON_FAIL(StreamUtil::readOrDiscardAll(stdErrorStream, 0, outStdError));
+
+ return SLANG_OK;
+}
+
+} // namespace Slang
diff --git a/source/core/slang-process-util.h b/source/core/slang-process-util.h
index af524940c..14ca208f8 100644
--- a/source/core/slang-process-util.h
+++ b/source/core/slang-process-util.h
@@ -2,52 +2,14 @@
#ifndef SLANG_PROCESS_UTIL_H
#define SLANG_PROCESS_UTIL_H
-#include "slang-string.h"
-#include "slang-list.h"
-
-#include "slang-string-escape-util.h"
+#include "slang-process.h"
namespace Slang {
-struct CommandLine
-{
- enum class ExecutableType
- {
- Unknown, ///< The executable is not specified
- Path, ///< The executable is set as a path
- Filename, ///< The executable is set as a filename
- };
-
- /// Add args - assumed unescaped
- void addArg(const String& in) { m_args.add(in); }
- void addArgs(const String* args, Int argsCount) { for (Int i = 0; i < argsCount; ++i) addArg(args[i]); }
-
- /// Find the index of an arg which is exact match for slice
- SLANG_INLINE Index findArgIndex(const UnownedStringSlice& slice) const { return m_args.indexOf(slice); }
-
- /// Set the executable path
- void setExecutablePath(const String& path) { m_executableType = ExecutableType::Path; m_executable = path; }
- void setExecutableFilename(const String& filename) { m_executableType = ExecutableType::Filename; m_executable = filename; }
-
- /// For handling args where the switch is placed directly in front of the path
- SLANG_INLINE void addPrefixPathArg(const char* prefix, const String& path, const char* pathPostfix = nullptr);
-
- /// Get the total number of args
- SLANG_FORCE_INLINE Index getArgCount() const { return m_args.getCount(); }
-
- /// Reset to the initial state
- void reset() { *this = CommandLine(); }
-
- /// Ctor
- CommandLine():m_executableType(ExecutableType::Unknown) {}
-
- ExecutableType m_executableType; ///< How the executable is specified
- String m_executable; ///< Executable to run. Note that the executable is never escaped.
- List<String> m_args; ///< The arguments (Stored *unescaped*)
-};
-
struct ExecuteResult
{
+ typedef int ResultCode;
+
void init()
{
resultCode = 0;
@@ -55,45 +17,24 @@ struct ExecuteResult
standardError = String();
}
- typedef int ResultCode;
ResultCode resultCode;
- Slang::String standardOutput;
- Slang::String standardError;
+ String standardOutput;
+ String standardError;
};
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();
-
- /// Output how the command line is executed on the target (with escaping and the such like)
- static String getCommandLineString(const CommandLine& commandLine);
-
/// Execute the command line
static SlangResult execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult);
- static uint64_t getClockFrequency();
+ /// Read from read from streams until process terminates.
+ /// Passing nullptr for a stream, will just discard what's in the stream
+ static SlangResult readUntilTermination(Process* process, List<Byte>* outStdOut, List<Byte>* stdError);
- static uint64_t getClockTick();
+ /// Read streams from process.
+ static SlangResult readUntilTermination(Process* process, ExecuteResult& outExecuteResult);
};
-// -----------------------------------------------------------------------
-SLANG_INLINE void CommandLine::addPrefixPathArg(const char* prefix, const String& path, const char* pathPostfix)
-{
- StringBuilder builder;
- builder << prefix << path;
- if (pathPostfix)
- {
- // Work out the path with the postfix
- builder << pathPostfix;
- }
- addArg(builder.ProduceString());
-}
-
}
#endif // SLANG_PROCESS_UTIL_H
diff --git a/source/core/slang-process.h b/source/core/slang-process.h
new file mode 100644
index 000000000..ddbe7cbf0
--- /dev/null
+++ b/source/core/slang-process.h
@@ -0,0 +1,73 @@
+// slang-process.h
+#ifndef SLANG_PROCESS_H
+#define SLANG_PROCESS_H
+
+#include "slang-string.h"
+#include "slang-list.h"
+#include "slang-stream.h"
+#include "slang-io.h"
+
+#include "slang-string-escape-util.h"
+
+#include "slang-command-line.h"
+
+namespace Slang {
+
+class Process : public RefObject
+{
+public:
+ enum class StreamType
+ {
+ ErrorOut,
+ StdOut,
+ StdIn,
+ CountOf,
+ };
+
+ typedef uint32_t Flags;
+ struct Flag
+ {
+ enum Enum : Flags
+ {
+ AttachDebugger = 0x01,
+ };
+ };
+
+ /// Get the stream for the type
+ Stream* getStream(StreamType type) const { return m_streams[Index(type)]; }
+ int32_t getReturnValue() const { return m_returnValue; }
+
+ /// True if the process has terminated
+ virtual bool isTerminated() = 0;
+ /// Blocks until the process has completed
+ virtual void waitForTermination() = 0;
+
+
+ /// 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();
+
+ /// Create a process using the executable/args defined from the commandLine
+ static SlangResult create(const CommandLine& commandLine, Process::Flags flags, RefPtr<Process>& outProcess);
+
+ /// Sleep the current thread for time specified in milliseconds. 0 indicates to OS ok to yield this thread.
+ static void sleepCurrentThread(Index timeInMs);
+
+ /// Get a standard stream
+ static SlangResult getStdStream(StreamType type, RefPtr<Stream>& out);
+
+ static uint64_t getClockFrequency();
+
+ static uint64_t getClockTick();
+
+protected:
+ int32_t m_returnValue = 0; ///< Value returned if process terminated
+ RefPtr<Stream> m_streams[Index(StreamType::CountOf)]; ///< Streams to communicate with the process
+};
+
+}
+
+#endif // SLANG_PROCESS_H
diff --git a/source/core/slang-stream.cpp b/source/core/slang-stream.cpp
index f938eb886..042b1d898 100644
--- a/source/core/slang-stream.cpp
+++ b/source/core/slang-stream.cpp
@@ -250,6 +250,16 @@ SlangResult FileStream::write(const void* buffer, size_t length)
return (bytesWritten == length) ? SLANG_OK : SLANG_FAIL;
}
+SlangResult FileStream::flush()
+{
+ if (m_handle && canWrite())
+ {
+ fflush(m_handle);
+ return SLANG_OK;
+ }
+ return SLANG_E_NOT_AVAILABLE;
+}
+
bool FileStream::canRead()
{
return ((int)m_fileAccess & (int)FileAccess::Read) != 0;
@@ -362,4 +372,270 @@ SlangResult OwnedMemoryStream::write(const void * buffer, size_t length)
return SLANG_OK;
}
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! BufferedReadStream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+void BufferedReadStream::_advanceStartIndex(Index byteCount)
+{
+ SLANG_ASSERT(Index(getCount()) >= byteCount && byteCount >= 0);
+ m_startIndex += byteCount;
+ if (getCount() == 0)
+ {
+ m_startIndex = 0;
+ }
+}
+
+Int64 BufferedReadStream::getPosition()
+{
+ return m_stream ? (m_stream->getPosition() - getCount()) : 0;
+}
+
+SlangResult BufferedReadStream::seek(SeekOrigin origin, Int64 offset)
+{
+ if (!m_stream)
+ {
+ return SLANG_FAIL;
+ }
+ // As it currently stands the data behind m_startIndex is the previous data.
+ // So we could seek backwards up to -m_startIndex.
+ // We don't worry about this here, for simplicity sake.
+
+ if (origin == SeekOrigin::End || origin == SeekOrigin::Start || offset < 0 || offset >= Int64(getCount()))
+ {
+ // Empty the buffer
+ m_startIndex = 0;
+ m_buffer.setCount(0);
+ // Seek on underlying stream
+ return m_stream->seek(origin, offset);
+ }
+
+ // We can just seek on the buffered data
+ _advanceStartIndex(Index(offset));
+ return SLANG_OK;
+}
+
+SlangResult BufferedReadStream::read(void* inBuffer, size_t length, size_t& outReadBytes)
+{
+ // If the buffer has no data and the read size is larger than the default read size - may as well just read directly into the output buffer
+ if (getCount() == 0 && length > m_defaultReadSize)
+ {
+ return m_stream->read(inBuffer, length, outReadBytes);
+ }
+
+ Byte* buffer = (Byte*)inBuffer;
+
+ size_t totalReadBytes = 0;
+ outReadBytes = 0;
+
+ // Do a read to fill the buffer.
+ SLANG_RETURN_ON_FAIL(update());
+
+ while (length > 0)
+ {
+ const size_t bufferCount = size_t(getCount());
+
+ if (bufferCount)
+ {
+ const size_t readCount = (bufferCount < length) ? bufferCount : length;
+
+ ::memcpy(buffer, getBuffer(), readCount);
+
+ _advanceStartIndex(Index(readCount));
+ buffer += readCount;
+ length -= readCount;
+
+ totalReadBytes += readCount;
+ }
+ else
+ {
+ if (m_stream == nullptr)
+ {
+ break;
+ }
+
+ // Read from underlying buffer
+ size_t readBytes;
+ SlangResult res = m_stream->read(buffer, length, readBytes);
+
+ outReadBytes = totalReadBytes + readBytes;
+ return res;
+ }
+ }
+
+ outReadBytes = totalReadBytes;
+ return SLANG_OK;
+}
+
+SlangResult BufferedReadStream::write(const void* buffer, size_t length)
+{
+ SLANG_UNUSED(buffer);
+ SLANG_UNUSED(length);
+
+ return SLANG_E_NOT_AVAILABLE;
+}
+
+bool BufferedReadStream::canRead()
+{
+ return getCount() > 0 || (m_stream && m_stream->canRead());
+}
+
+bool BufferedReadStream::canWrite()
+{
+ return false;
+}
+
+void BufferedReadStream::close()
+{
+ if (m_stream)
+ {
+ m_stream->close();
+ m_stream.setNull();
+ }
+}
+
+bool BufferedReadStream::isEnd()
+{
+ return getCount() == 0 && (m_stream == nullptr || m_stream->isEnd());
+}
+
+SlangResult BufferedReadStream::flush()
+{
+ return SLANG_E_NOT_AVAILABLE;
+}
+
+SlangResult BufferedReadStream::update()
+{
+ if (m_stream == nullptr)
+ {
+ // Should this return an error?
+ return SLANG_OK;
+ }
+
+ {
+ // How much buffer space do we have. We need at least m_defaultReadSize
+ const size_t remainingCount = size_t(m_buffer.getCapacity() - m_buffer.getCount());
+
+ // Repeat until we have enough space
+ while (remainingCount < m_defaultReadSize)
+ {
+ // If there is anything in the buffer shift it all down
+ if (m_startIndex > 0)
+ {
+ Byte* buffer = m_buffer.getBuffer();
+ const Index count = getCount();
+ if (count > 0)
+ {
+ ::memmove(buffer, buffer + m_startIndex, count);
+ }
+
+ m_buffer.setCount(count);
+ m_startIndex = 0;
+ }
+ else
+ {
+ // Make sure we have the space
+ const Index prevCount = m_buffer.getCount();
+ m_buffer.setCount(prevCount + m_defaultReadSize);
+ m_buffer.setCount(prevCount);
+ }
+ }
+ SLANG_ASSERT(size_t(m_buffer.getCapacity() - m_buffer.getCount()) >= m_defaultReadSize);
+ }
+
+ {
+ const Index prevCount = m_buffer.getCount();
+ m_buffer.setCount(prevCount + m_defaultReadSize);
+
+ size_t readBytes = 0;
+
+ const SlangResult res = m_stream->read(m_buffer.getBuffer() + prevCount, m_defaultReadSize, readBytes);
+
+ m_buffer.setCount(prevCount + Index(readBytes));
+
+ return res;
+ }
+}
+
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! StreamUtil !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+/* static */SlangResult StreamUtil::readAll(Stream* stream, size_t readSize, List<Byte>& ioBytes)
+{
+ while (!stream->isEnd())
+ {
+ SLANG_RETURN_ON_FAIL(read(stream, readSize, ioBytes));
+ }
+
+ return SLANG_OK;
+}
+
+/* static */SlangResult StreamUtil::read(Stream* stream, size_t readSize, List<Byte>& ioBytes)
+{
+ readSize = (readSize <= 0) ? 1024 : readSize;
+
+ while (true)
+ {
+ const Index prevCount = ioBytes.getCount();
+ ioBytes.setCount(prevCount + readSize);
+
+ size_t readBytesCount;
+ SLANG_RETURN_ON_FAIL(stream->read(ioBytes.getBuffer() + prevCount, readSize, readBytesCount));
+ ioBytes.setCount(prevCount + Index(readBytesCount));
+
+ if (readBytesCount == 0)
+ {
+ return SLANG_OK;
+ }
+ }
+}
+
+/* static */SlangResult StreamUtil::discard(Stream* stream)
+{
+ Byte buf[1024];
+ const Index bufSize = SLANG_COUNT_OF(buf);
+
+ while (true)
+ {
+ size_t readBytesCount;
+ SLANG_RETURN_ON_FAIL(stream->read(buf, bufSize, readBytesCount));
+
+ if (readBytesCount == 0)
+ {
+ return SLANG_OK;
+ }
+ }
+}
+
+/* static */SlangResult StreamUtil::discardAll(Stream* stream)
+{
+ while (!stream->isEnd())
+ {
+ SLANG_RETURN_ON_FAIL(discard(stream));
+ }
+ return SLANG_OK;
+}
+
+
+/* static */SlangResult StreamUtil::readOrDiscard(Stream* stream, size_t readSize, List<Byte>* ioBytes)
+{
+ if (ioBytes)
+ {
+ return read(stream, readSize, *ioBytes);
+ }
+ else
+ {
+ return discard(stream);
+ }
+}
+
+/* static */SlangResult StreamUtil::readOrDiscardAll(Stream* stream, size_t readSize, List<Byte>* ioBytes)
+{
+ if (ioBytes)
+ {
+ return readAll(stream, readSize, *ioBytes);
+ }
+ else
+ {
+ return discardAll(stream);
+ }
+}
+
} // namespace Slang
diff --git a/source/core/slang-stream.h b/source/core/slang-stream.h
index e33e1c601..f6c9f54a3 100644
--- a/source/core/slang-stream.h
+++ b/source/core/slang-stream.h
@@ -43,6 +43,9 @@ public:
/// Implies any pending data is flushed.
virtual void close() = 0;
+ /// Only applicable for write streams, flushes any buffers to underlying representation (such as pipe, or file)
+ virtual SlangResult flush() = 0;
+
/// Helper function that will also *fail* if the specified amount of bytes aren't read.
SlangResult readExactly(void* buffer, size_t length);
};
@@ -76,6 +79,7 @@ public:
virtual bool canRead() SLANG_OVERRIDE { return (int(m_access) & int(FileAccess::Read)) != 0; }
virtual bool canWrite() SLANG_OVERRIDE { return (int(m_access) & int(FileAccess::Write)) != 0; }
virtual void close() SLANG_OVERRIDE { m_access = FileAccess::None; }
+ virtual SlangResult flush() SLANG_OVERRIDE { return canWrite() ? SLANG_OK : SLANG_E_NOT_AVAILABLE; }
/// Get the contents
ConstArrayView<uint8_t> getContents() const { return ConstArrayView<uint8_t>(m_contents, m_contentsSize); }
@@ -159,6 +163,7 @@ public:
virtual bool canWrite() SLANG_OVERRIDE;
virtual void close() SLANG_OVERRIDE;
virtual bool isEnd() SLANG_OVERRIDE;
+ virtual SlangResult flush() SLANG_OVERRIDE;
FileStream();
@@ -175,6 +180,62 @@ private:
bool m_endReached = false;
};
+/* A simple BufferedReader. The valid data is between m_startIndex and getCount().
+Can be used as a buffer to build up a result from a stream in memory using 'update' to read to the appropriate buffer size.
+*/
+class BufferedReadStream : public Stream
+{
+public:
+ typedef Stream Super;
+
+ virtual Int64 getPosition() SLANG_OVERRIDE;
+ virtual SlangResult seek(SeekOrigin origin, Int64 offset) SLANG_OVERRIDE;
+ virtual SlangResult read(void* buffer, size_t length, size_t& outReadBytes) SLANG_OVERRIDE;
+ virtual SlangResult write(const void* buffer, size_t length) SLANG_OVERRIDE;
+ virtual bool canRead() SLANG_OVERRIDE;
+ virtual bool canWrite() SLANG_OVERRIDE;
+ virtual void close() SLANG_OVERRIDE;
+ virtual bool isEnd() SLANG_OVERRIDE;
+ virtual SlangResult flush() SLANG_OVERRIDE;
+
+ /// Will read assuming backing stream is
+ SlangResult update();
+
+ Byte* getBuffer() { return m_buffer.getBuffer() + m_startIndex; }
+ const Byte* getBuffer() const { return m_buffer.getBuffer() + m_startIndex; }
+
+ size_t getCount() const { return m_buffer.getCount() - m_startIndex; }
+
+ ConstArrayView<Byte> getView() const { return ConstArrayView<Byte>(getBuffer(), Index(getCount())); }
+ ArrayView<Byte> getView() { return ArrayView<Byte>(getBuffer(), Index(getCount())); }
+
+protected:
+ void _advanceStartIndex(Index byteCount);
+
+ size_t m_defaultReadSize = 1024; ///< When initiating a read the default read size
+ List<Byte> m_buffer; ///< Holds the characters
+ Index m_startIndex; ///< The start index
+ RefPtr<Stream> m_stream; ///< Stream that is being read from
+};
+
+struct StreamUtil
+{
+ /// Appends all bytes that can be read from stream into bytes
+ static SlangResult readAll(Stream* stream, size_t readSize, List<Byte>& ioBytes);
+
+ /// Read as much as can be read until a 0 sized read, or an error and append onto ioBytes
+ /// Read size controls the size of each buffer read. Passing 0, will use the default read size.
+ static SlangResult read(Stream* stream, size_t readSize, List<Byte>& ioBytes);
+
+ static SlangResult discard(Stream* stream);
+
+ static SlangResult discardAll(Stream* stream);
+
+ static SlangResult readOrDiscard(Stream* stream, size_t readSize, List<Byte>* ioBytes);
+ static SlangResult readOrDiscardAll(Stream* stream, size_t readSize, List<Byte>* ioBytes);
+};
+
+
} // namespace Slang
#endif
diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp
index b2886c413..5282f01a6 100644
--- a/source/core/slang-string-util.cpp
+++ b/source/core/slang-string-util.cpp
@@ -350,6 +350,60 @@ ComPtr<ISlangBlob> StringUtil::createStringBlob(const String& string)
return (fromChar == toChar || string.indexOf(fromChar) == Index(-1)) ? string : calcCharReplaced(string.getUnownedSlice(), fromChar, toChar);
}
+/* static */void StringUtil::appendStandardLines(const UnownedStringSlice& text, StringBuilder& out)
+{
+ const char* cur = text.begin();
+ const char* start = cur;
+ const char* const end = text.end();
+
+ while (cur < end)
+ {
+ const char c = *cur;
+ switch (c)
+ {
+ case '\n':
+ {
+ ++cur;
+ if (cur < end && *cur == '\r')
+ {
+ // If we have following \r, we should append with \n
+ // Append (including \n)
+ out.append(start, cur);
+ // Skip the \r
+ start = ++cur;
+ }
+ else
+ {
+ // If not, we don't need to append because just \n is 'standard', and everything remaining
+ // is appended at the end
+ }
+ break;
+ }
+ case '\r':
+ {
+ out.append(start, cur);
+ out.appendChar('\n');
+
+ ++cur;
+ // If next is \n, we want to skip that
+ cur += Index(cur < end && *cur == '\n');
+ start = cur;
+ break;
+ }
+ default:
+ {
+ cur++;
+ break;
+ }
+ }
+ }
+
+ if (start < end)
+ {
+ out.append(start, end);
+ }
+}
+
/* static */bool StringUtil::extractLine(UnownedStringSlice& ioText, UnownedStringSlice& outLine)
{
char const*const begin = ioText.begin();
diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h
index 6a0794082..03f503ed2 100644
--- a/source/core/slang-string-util.h
+++ b/source/core/slang-string-util.h
@@ -78,6 +78,9 @@ struct StringUtil
/// Create a blob from a string
static ComPtr<ISlangBlob> createStringBlob(const String& string);
+ /// Given input text outputs with standardized line endings. Ie \n\r -> \n
+ static void appendStandardLines(const UnownedStringSlice& text, StringBuilder& out);
+
/// Extracts a line and stores the remaining text in ioText, and the line in outLine. Returns true if has a line.
///
/// As well as indicating end of text with the return value, at the end of all the text a 'special' null UnownedStringSlice with a null 'begin'
diff --git a/source/core/unix/slang-unix-process-util.cpp b/source/core/unix/slang-unix-process-util.cpp
deleted file mode 100644
index af49eec37..000000000
--- a/source/core/unix/slang-unix-process-util.cpp
+++ /dev/null
@@ -1,246 +0,0 @@
-// slang-unix-process-util.cpp
-#include "../slang-process-util.h"
-
-#include "../slang-common.h"
-#include "../slang-string-util.h"
-#include "../slang-string-escape-util.h"
-#include "../slang-memory-arena.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-
-//#include <dirent.h>
-#include <errno.h>
-#include <poll.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <time.h>
-
-namespace Slang {
-
-
-/* static */UnownedStringSlice ProcessUtil::getExecutableSuffix()
-{
-#if __CYGWIN__
- return UnownedStringSlice::fromLiteral(".exe");
-#else
- return UnownedStringSlice::fromLiteral("");
-#endif
-}
-
-/* static */StringEscapeHandler* ProcessUtil::getEscapeHandler()
-{
- 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;
- StringEscapeUtil::appendMaybeQuoted(escapeHandler, commandLine.m_executable.getUnownedSlice(), cmd);
- for (const auto& arg : commandLine.m_args)
- {
- cmd << " ";
-
- StringEscapeUtil::appendMaybeQuoted(escapeHandler, arg.getUnownedSlice(), cmd);
- }
- return cmd.ToString();
-}
-
-/* static */SlangResult ProcessUtil::execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult)
-{
- outExecuteResult.init();
-
- List<char const*> argPtrs;
-
- // Add the command
- argPtrs.add(commandLine.m_executable.getBuffer());
-
- // Add all the args - they don't need any explicit escaping
- for (auto arg : commandLine.m_args)
- {
- // All args for this target must be unescaped (as they are in CommandLine)
- argPtrs.add(arg.getBuffer());
- }
-
- // Terminate with a null
- argPtrs.add(nullptr);
-
- int stdoutPipe[2];
- int stderrPipe[2];
-
- if (pipe(stdoutPipe) == -1)
- {
- fprintf(stderr, "error: `pipe` failed\n");
- return SLANG_FAIL;
- }
-
- if (pipe(stderrPipe) == -1)
- {
- fprintf(stderr, "error: `pipe` failed\n");
- return SLANG_FAIL;
- }
-
- pid_t childProcessID = fork();
- if (childProcessID == -1)
- {
- fprintf(stderr, "error: `fork` failed\n");
- return SLANG_FAIL;
- }
-
- if (childProcessID == 0)
- {
- // We are the child process.
-
- dup2(stdoutPipe[1], STDOUT_FILENO);
- dup2(stderrPipe[1], STDERR_FILENO);
-
- close(stdoutPipe[0]);
- close(stdoutPipe[1]);
-
- close(stderrPipe[0]);
- close(stderrPipe[1]);
-
- execvp(argPtrs[0], (char* const*)&argPtrs[0]);
-
- // If we get here, then `exec` failed
- fprintf(stderr, "error: `exec` failed\n");
- return SLANG_FAIL;
- }
- else
- {
- // We are the parent process
-
- close(stdoutPipe[1]);
- close(stderrPipe[1]);
-
- int stdoutFD = stdoutPipe[0];
- int stderrFD = stderrPipe[0];
-
- pollfd pollInfos[2];
- nfds_t pollInfoCount = 2;
-
- pollInfos[0].fd = stdoutFD;
- pollInfos[0].events = POLLIN;
- pollInfos[0].revents = 0;
- pollInfos[1].fd = stderrFD;
- pollInfos[1].events = POLLIN;
- pollInfos[1].revents = 0;
-
- int remainingCount = 2;
- int iterations = 0;
- while (remainingCount)
- {
- // Safeguard against infinite loop:
- iterations++;
- if (iterations > 10000)
- {
- fprintf(stderr, "poll(): %d iterations\n", iterations);
- return SLANG_FAIL;
- }
-
- // Set a timeout of 100 seconds;
- // we really shouldn't wait too long...
- int pollTimeout = 100000;
- int pollResult = poll(pollInfos, pollInfoCount, pollTimeout);
- if (pollResult <= 0)
- {
- // If there was a signal that got in
- // the way, then retry...
- if (pollResult == -1 && errno == EINTR)
- continue;
-
- // timeout or error...
- fprintf(stderr, "error: `poll` failed or timed out\n");
- return SLANG_FAIL;
- }
-
- enum { kBufferSize = 1024 };
- char buffer[kBufferSize];
-
- if (pollInfos[0].revents)
- {
- auto count = read(stdoutFD, buffer, kBufferSize);
- if (count <= 0)
- {
- // end-of-file
- close(stdoutFD);
- pollInfos[0].fd = -1;
- remainingCount--;
- }
-
- outExecuteResult.standardOutput.append(buffer, buffer + count);
- }
-
- if (pollInfos[1].revents)
- {
- auto count = read(stderrFD, buffer, kBufferSize);
- if (count <= 0)
- {
- // end-of-file
- close(stderrFD);
- pollInfos[1].fd = -1;
- remainingCount--;
- }
-
- outExecuteResult.standardError.append(buffer, buffer + count);
- }
- }
-
- int childStatus = 0;
- iterations = 0;
- for (;;)
- {
- // Safeguard against infinite loop:
- iterations++;
- if (iterations > 10000)
- {
- fprintf(stderr, "waitpid(): %d iterations\n", iterations);
- return SLANG_FAIL;
- }
-
- pid_t terminatedProcessID = waitpid(childProcessID, &childStatus, 0);
- if (terminatedProcessID == -1)
- {
- fprintf(stderr, "error: `waitpid` failed\n");
- return SLANG_FAIL;
- }
-
- if (terminatedProcessID == childProcessID)
- {
- if (WIFEXITED(childStatus))
- {
- outExecuteResult.resultCode = (int)(int8_t)WEXITSTATUS(childStatus);
- }
- else
- {
- outExecuteResult.resultCode = 1;
- }
- return SLANG_OK;
- }
- }
- }
-
- return SLANG_FAIL;
-}
-
-/* static */uint64_t ProcessUtil::getClockFrequency()
-{
- return 1000000000;
-}
-
-/* static */uint64_t ProcessUtil::getClockTick()
-{
- struct timespec now;
- clock_gettime(CLOCK_MONOTONIC, &now);
- return uint64_t(now.tv_sec) * 1000000000 + now.tv_nsec;
-}
-
-} // namespace Slang
diff --git a/source/core/unix/slang-unix-process.cpp b/source/core/unix/slang-unix-process.cpp
new file mode 100644
index 000000000..083160f02
--- /dev/null
+++ b/source/core/unix/slang-unix-process.cpp
@@ -0,0 +1,430 @@
+// slang-unix-process.cpp
+#include "../slang-process.h"
+
+#include "../slang-common.h"
+#include "../slang-string-util.h"
+#include "../slang-string-escape-util.h"
+#include "../slang-memory-arena.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+//#include <dirent.h>
+#include <errno.h>
+#include <poll.h>
+#include <fcntl.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <time.h>
+
+namespace Slang {
+
+class UnixProcess : public Process
+{
+public:
+ // Process
+ virtual bool isTerminated() SLANG_OVERRIDE;
+ virtual void waitForTermination() SLANG_OVERRIDE;
+
+ UnixProcess(pid_t pid, Stream*const* streams);
+
+protected:
+ /// Returns true if terminated
+ bool _updateTerminationState(int options);
+
+ bool m_isTerminated = false; ///< True if ths process is terminated
+ pid_t m_pid; ///< The process id
+};
+
+class UnixPipeStream : public Stream
+{
+public:
+ typedef UnixPipeStream ThisType;
+
+ // Stream
+ virtual Int64 getPosition() SLANG_OVERRIDE { return 0; }
+ virtual SlangResult seek(SeekOrigin origin, Int64 offset) SLANG_OVERRIDE { SLANG_UNUSED(origin); SLANG_UNUSED(offset); return SLANG_E_NOT_AVAILABLE; }
+ virtual SlangResult read(void* buffer, size_t length, size_t& outReadBytes) SLANG_OVERRIDE;
+ virtual SlangResult write(const void* buffer, size_t length) SLANG_OVERRIDE;
+ virtual bool isEnd() SLANG_OVERRIDE { return m_isClosed; }
+ virtual bool canRead() SLANG_OVERRIDE { return _has(FileAccess::Read) && !m_isClosed; }
+ virtual bool canWrite() SLANG_OVERRIDE { return _has(FileAccess::Write) && !m_isClosed; }
+ virtual void close() SLANG_OVERRIDE;
+ virtual SlangResult flush() SLANG_OVERRIDE;
+
+ UnixPipeStream(int fd, FileAccess access, bool isOwned) :
+ m_fd(fd),
+ m_access(access),
+ m_isOwned(isOwned),
+ m_isClosed(false)
+ {
+ }
+
+protected:
+ /// This read file descriptor non blocking. Doing so will change the behavior of
+ /// read - it can fail and return an error indicating there is no data, instead of blocking.
+ /// Currently this mechanism isn't used, as checking via poll seemed to work.
+ void _setReadNonBlocking()
+ {
+ // Makes non blocking
+ if (_has(FileAccess::Read))
+ {
+ // Make non blocking, for read
+ fcntl(m_fd, F_SETFL, fcntl(m_fd, F_GETFL) | O_NONBLOCK);
+ }
+ }
+ bool _has(FileAccess access) const { return (Index(access) & Index(m_access)) != 0; }
+
+ bool m_isClosed; ///< If true this stream has been closed (ie cannot read/write to anymore)
+ bool m_isOwned; ///< True if m_fd is owned by this object.
+ FileAccess m_access; ///< Access allowed to this stream - either Read or Write
+ int m_fd; /// The 'file descriptor' for the pipe
+};
+
+/* !!!!!!!!!!!!!!!!!!!!!! UnixProcess !!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+UnixProcess::UnixProcess(pid_t pid, Stream* const* streams):
+ m_pid(pid)
+{
+ // Set to an 'odd value'
+ m_returnValue = -1;
+
+ for (Index i = 0; i < SLANG_COUNT_OF(m_streams); ++i)
+ {
+ m_streams[i] = streams[i];
+ }
+}
+
+bool UnixProcess::_updateTerminationState(int options)
+{
+ if (!m_isTerminated)
+ {
+ int childStatus;
+ const pid_t terminatedPid = waitpid(m_pid, &childStatus, options);
+ if (terminatedPid == -1)
+ {
+ // Guess we should just mark as terminated
+ m_isTerminated = true;
+
+ fprintf(stderr, "error: `waitpid` failed\n");
+ }
+ else if (terminatedPid == m_pid)
+ {
+ if (WIFEXITED(childStatus))
+ {
+ m_returnValue = (int)(int8_t)WEXITSTATUS(childStatus);
+ }
+ m_isTerminated = true;
+ }
+ }
+ return m_isTerminated;
+}
+
+bool UnixProcess::isTerminated()
+{
+ if (m_isTerminated)
+ {
+ return true;
+ }
+ return _updateTerminationState(WNOHANG);
+}
+
+void UnixProcess::waitForTermination()
+{
+ while (!_updateTerminationState(0));
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!! UnixPipeStream !!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+void UnixPipeStream::close()
+{
+ if (!m_isClosed)
+ {
+ if (m_isOwned)
+ {
+ ::close(m_fd);
+ }
+
+ m_isClosed = true;
+ // Make something hopefully invalid
+ m_fd = -1;
+ }
+}
+
+SlangResult UnixPipeStream::flush()
+{
+#if 0
+ // https://stackoverflow.com/questions/43184035/flushing-pipe-without-closing-in-c
+ // Makes the case that flushing is not applicable with pipes.
+ if (canWrite())
+ {
+ // We might want to use
+ ::fsync(m_fd);
+ }
+#endif
+ return SLANG_OK;
+}
+
+SlangResult UnixPipeStream::read(void* buffer, size_t length, size_t& outReadBytes)
+{
+ outReadBytes = 0;
+
+ if (!_has(FileAccess::Read))
+ {
+ return SLANG_E_NOT_AVAILABLE;
+ }
+ if (m_isClosed)
+ {
+ return SLANG_OK;
+ }
+
+ // Check if it's hung up.
+ pollfd pollInfo;
+
+ pollInfo.fd = m_fd;
+ pollInfo.events = POLLIN | POLLHUP;
+ pollInfo.revents = 0;
+
+ // https://linux.die.net/man/2/poll
+
+ // Return immediately
+ const int pollTimeout = 0;
+
+ const int pollResult = ::poll(&pollInfo, 1, pollTimeout);
+ if (pollResult < 0)
+ {
+ return SLANG_FAIL;
+ }
+
+ // If there is data read that first
+ if (pollInfo.revents & POLLIN)
+ {
+ auto count = ::read(m_fd, buffer, length);
+
+ // If it's -1 it seems like an error
+ if (count == -1)
+ {
+ const int err = errno;
+
+ // On non blocking pipe these indicate there could be more to come
+ if (err == EAGAIN || err == EWOULDBLOCK)
+ {
+ return SLANG_OK;
+ }
+ // Okay - guess we have an error then
+ return SLANG_FAIL;
+ }
+
+ outReadBytes = size_t(count);
+ return SLANG_OK;
+ }
+
+ if (pollInfo.revents & POLLHUP)
+ {
+ close();
+ }
+
+ return SLANG_OK;
+}
+
+SlangResult UnixPipeStream::write(const void* buffer, size_t length)
+{
+ if (!_has(FileAccess::Write))
+ {
+ return SLANG_E_NOT_AVAILABLE;
+ }
+ if (m_isClosed)
+ {
+ // The pipe is closed
+ return SLANG_FAIL;
+ }
+
+ pollfd pollInfo;
+
+ pollInfo.fd = m_fd;
+ pollInfo.events = POLLHUP;
+ pollInfo.revents = 0;
+
+ // https://linux.die.net/man/2/poll
+
+ // Return immediately
+ const int pollTimeout = 0;
+
+ int pollResult = ::poll(&pollInfo, 1, pollTimeout);
+ if (pollResult < 0)
+ {
+ return SLANG_FAIL;
+ }
+
+ if (pollInfo.revents & POLLHUP)
+ {
+ close();
+ return SLANG_FAIL;
+ }
+
+ const ssize_t writeResult = ::write(m_fd, buffer, length);
+
+ if (writeResult < 0 || writeResult != length)
+ {
+ return SLANG_FAIL;
+ }
+
+ return SLANG_OK;
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!! Process !!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+/* static */UnownedStringSlice Process::getExecutableSuffix()
+{
+#if __CYGWIN__
+ return UnownedStringSlice::fromLiteral(".exe");
+#else
+ return UnownedStringSlice::fromLiteral("");
+#endif
+}
+
+/* static */StringEscapeHandler* Process::getEscapeHandler()
+{
+ return StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space);
+}
+
+/* static */SlangResult Process::create(const CommandLine& commandLine, Process::Flags flags, RefPtr<Process>& outProcess)
+{
+ List<char const*> argPtrs;
+
+ // Add the command
+ argPtrs.add(commandLine.m_executable.getBuffer());
+
+ // Add all the args - they don't need any explicit escaping
+ for (auto arg : commandLine.m_args)
+ {
+ // All args for this target must be unescaped (as they are in CommandLine)
+ argPtrs.add(arg.getBuffer());
+ }
+
+ // Terminate with a null
+ argPtrs.add(nullptr);
+
+ int stdoutPipe[2];
+ int stderrPipe[2];
+ int stdinPipe[2];
+
+ if (pipe(stdoutPipe) == -1 || pipe(stderrPipe) == -1 || pipe(stdinPipe) == -1)
+ {
+ fprintf(stderr, "error: `pipe` failed\n");
+ return SLANG_FAIL;
+ }
+
+ pid_t childPid = fork();
+ if (childPid == -1)
+ {
+ fprintf(stderr, "error: `fork` failed\n");
+ return SLANG_FAIL;
+ }
+
+ if (childPid == 0)
+ {
+ // We are the child process.
+
+ // Duplicate into standard handles
+ dup2(stdoutPipe[1], STDOUT_FILENO);
+ dup2(stderrPipe[1], STDERR_FILENO);
+ dup2(stdinPipe[0], STDIN_FILENO);
+
+ // Close all of the handles
+ ::close(stdoutPipe[0]);
+ ::close(stdoutPipe[1]);
+
+ ::close(stderrPipe[0]);
+ ::close(stderrPipe[1]);
+
+ ::close(stdinPipe[0]);
+ ::close(stdinPipe[1]);
+
+ ::execvp(argPtrs[0], (char* const*)&argPtrs[0]);
+
+ // If we get here, then `exec` failed
+
+ // NOTE! Because we have dup2 into STDERR_FILENO, this error will *not* generally appear on
+ // the terminal but in the stderrPipe.
+ fprintf(stderr, "error: `exec` failed\n");
+
+ return SLANG_FAIL;
+ }
+ else
+ {
+ // We are the parent process
+ ::close(stdoutPipe[1]);
+ ::close(stderrPipe[1]);
+ ::close(stdinPipe[0]);
+
+ RefPtr<Stream> streams[Index(Process::StreamType::CountOf)];
+
+ // Previously code didn't need to close, so we'll make stream not own the handles
+ streams[Index(Process::StreamType::StdOut)] = new UnixPipeStream(stdoutPipe[0], FileAccess::Read, true);
+ streams[Index(Process::StreamType::ErrorOut)] = new UnixPipeStream(stderrPipe[0], FileAccess::Read, true);
+ streams[Index(Process::StreamType::StdIn)] = new UnixPipeStream(stdinPipe[1], FileAccess::Write, true);
+
+ outProcess = new UnixProcess(childPid, streams[0].readRef());
+ return SLANG_OK;
+ }
+}
+
+/* static */uint64_t Process::getClockFrequency()
+{
+ return 1000000000;
+}
+
+/* static */uint64_t Process::getClockTick()
+{
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ return uint64_t(now.tv_sec) * 1000000000 + now.tv_nsec;
+}
+
+/* static */void Process::sleepCurrentThread(Index timeInMs)
+{
+ struct timespec timeSpec;
+
+ if (timeInMs > 0)
+ {
+ timeSpec.tv_sec = timeInMs / 1000;
+ timeSpec.tv_nsec = (timeInMs % 1000) * 1000 * 1000;
+ }
+ else
+ {
+ timeSpec.tv_sec = 0;
+ timeSpec.tv_nsec = 0;
+ }
+ nanosleep(&timeSpec, nullptr);
+}
+
+/* static */SlangResult Process::getStdStream(StreamType type, RefPtr<Stream>& out)
+{
+ switch (type)
+ {
+ case StreamType::StdIn:
+ {
+ out = new UnixPipeStream(STDIN_FILENO, FileAccess::Read, false);
+ break;
+ }
+ case StreamType::StdOut:
+ {
+ out = new UnixPipeStream(STDOUT_FILENO, FileAccess::Write, false);
+ break;
+ }
+ case StreamType::ErrorOut:
+ {
+ out = new UnixPipeStream(STDERR_FILENO, FileAccess::Write, false);
+ break;
+ }
+ default: return SLANG_FAIL;
+ }
+ return SLANG_OK;
+}
+
+} // namespace Slang
diff --git a/source/core/windows/slang-win-process-util.cpp b/source/core/windows/slang-win-process-util.cpp
deleted file mode 100644
index 4039361a4..000000000
--- a/source/core/windows/slang-win-process-util.cpp
+++ /dev/null
@@ -1,333 +0,0 @@
-// slang-win-process-util.cpp
-#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.
-// TODO: We could try to avoid including this at all, but it would
-// mean trying to hide certain struct layouts, which would add
-// more dynamic allocation.
-# define WIN32_LEAN_AND_MEAN
-# define NOMINMAX
-# include <Windows.h>
-# undef WIN32_LEAN_AND_MEAN
-# undef NOMINMAX
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-
-namespace Slang {
-
-namespace { // anonymous
-
-struct ThreadInfo
-{
- HANDLE file;
- String output;
-};
-
-// Has behavior very similar to unique_ptr - assignment is a move.
-class WinHandle
-{
-public:
- /// Detach the encapsulated handle. Returns the handle (which now must be externally handled)
- HANDLE detach() { HANDLE handle = m_handle; m_handle = nullptr; return handle; }
-
- /// Return as a handle
- operator HANDLE() const { return m_handle; }
-
- /// Assign
- void operator=(HANDLE handle) { setNull(); m_handle = handle; }
- void operator=(WinHandle&& rhs) { HANDLE handle = m_handle; m_handle = rhs.m_handle; rhs.m_handle = handle; }
-
- /// Get ready for writing
- SLANG_FORCE_INLINE HANDLE* writeRef() { setNull(); return &m_handle; }
- /// Get for read access
- SLANG_FORCE_INLINE const HANDLE* readRef() const { return &m_handle; }
-
- void setNull()
- {
- if (m_handle)
- {
- CloseHandle(m_handle);
- m_handle = nullptr;
- }
- }
-
- /// Ctor
- WinHandle(HANDLE handle = nullptr):m_handle(handle) {}
- WinHandle(WinHandle&& rhs):m_handle(rhs.m_handle) { rhs.m_handle = nullptr; }
-
- /// Dtor
- ~WinHandle() { setNull(); }
-
-private:
-
- WinHandle(const WinHandle&) = delete;
- void operator=(const WinHandle& rhs) = delete;
-
- HANDLE m_handle;
-};
-
-} // anonymous
-
-static DWORD WINAPI _readerThreadProc(LPVOID threadParam)
-{
- ThreadInfo* info = (ThreadInfo*)threadParam;
- HANDLE file = info->file;
-
- static const int kChunkSize = 1024;
- char buffer[kChunkSize];
-
- StringBuilder outputBuilder;
-
- // We need to re-write the output to deal with line
- // endings, so we check for paired '\r' and '\n'
- // characters, which may span chunks.
- int prevChar = -1;
-
- for (;;)
- {
- DWORD bytesRead = 0;
- BOOL readResult = ReadFile(file, buffer, kChunkSize, &bytesRead, nullptr);
-
- const DWORD lastError = GetLastError();
- if (lastError == ERROR_BROKEN_PIPE)
- {
- break;
- }
-
- if (!readResult)
- {
- break;
- }
-
-
- // walk the buffer and rewrite to eliminate '\r' '\n' pairs
- char* readCursor = buffer;
- char const* end = buffer + bytesRead;
- char* writeCursor = buffer;
-
- while (readCursor != end)
- {
- int p = prevChar;
- int c = *readCursor++;
- prevChar = c;
- switch (c)
- {
- case '\r': case '\n':
- // swallow input if '\r' and '\n' appear in sequence
- if ((p ^ c) == ('\r' ^ '\n'))
- {
- // but don't swallow the next byte
- prevChar = -1;
- continue;
- }
- // always replace '\r' with '\n'
- c = '\n';
- break;
-
- default:
- break;
- }
-
- *writeCursor++ = (char)c;
- }
- bytesRead = (DWORD)(writeCursor - buffer);
-
- // Note: Current "core" implementation gives no way to know
- // the length of the buffer, so we ultimately have
- // to just assume null termination...
- outputBuilder.Append(buffer, bytesRead);
- }
-
- info->output = outputBuilder.ProduceString();
-
- return 0;
-}
-
-/* static */StringEscapeHandler* ProcessUtil::getEscapeHandler()
-{
- return StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space);
-}
-
-/* static */UnownedStringSlice ProcessUtil::getExecutableSuffix()
-{
- return UnownedStringSlice::fromLiteral(".exe");
-}
-
-/* static */String ProcessUtil::getCommandLineString(const CommandLine& commandLine)
-{
- auto escapeHandler = getEscapeHandler();
-
- StringBuilder cmd;
- StringEscapeUtil::appendMaybeQuoted(escapeHandler, commandLine.m_executable.getUnownedSlice(), cmd);
-
- for (const auto& arg : commandLine.m_args)
- {
- cmd << " ";
- StringEscapeUtil::appendMaybeQuoted(escapeHandler, arg.getUnownedSlice(), cmd);
- }
- return cmd.ToString();
-}
-
-#define SLANG_RETURN_FAIL_ON_FALSE(x) if (!(x)) return SLANG_FAIL;
-
-/* static */SlangResult ProcessUtil::execute(const CommandLine& commandLine, ExecuteResult& outExecuteResult)
-{
- outExecuteResult.init();
-
- SECURITY_ATTRIBUTES securityAttributes;
- securityAttributes.nLength = sizeof(securityAttributes);
- securityAttributes.lpSecurityDescriptor = nullptr;
- securityAttributes.bInheritHandle = true;
-
- WinHandle childStdOutRead;
- WinHandle childStdErrRead;
- WinHandle childStdInWrite;
-
- // Now we can actually get around to starting a process
- PROCESS_INFORMATION processInfo;
- ZeroMemory(&processInfo, sizeof(processInfo));
- {
- WinHandle childStdOutWrite;
- WinHandle childStdErrWrite;
- WinHandle childStdInRead;
-
- {
- WinHandle childStdOutReadTmp;
- WinHandle childStdErrReadTmp;
- WinHandle childStdInWriteTmp;
- // create stdout pipe for child process
- SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdOutReadTmp.writeRef(), childStdOutWrite.writeRef(), &securityAttributes, 0));
- // create stderr pipe for child process
- SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdErrReadTmp.writeRef(), childStdErrWrite.writeRef(), &securityAttributes, 0));
- // create stdin pipe for child process
- SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdInRead.writeRef(), childStdInWriteTmp.writeRef(), &securityAttributes, 0));
-
- HANDLE currentProcess = GetCurrentProcess();
-
- // create a non-inheritable duplicate of the stdout reader
- SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdOutReadTmp, currentProcess, childStdOutRead.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS));
- // create a non-inheritable duplicate of the stderr reader
- SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdErrReadTmp, currentProcess, childStdErrRead.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS));
- // create a non-inheritable duplicate of the stdin writer
- SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdInWriteTmp, currentProcess, childStdInWrite.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS));
- }
-
-
- // TODO: switch to proper wide-character versions of these...
- STARTUPINFOW startupInfo;
- ZeroMemory(&startupInfo, sizeof(startupInfo));
- startupInfo.cb = sizeof(startupInfo);
- startupInfo.hStdError = childStdErrWrite;
- startupInfo.hStdOutput = childStdOutWrite;
- startupInfo.hStdInput = childStdInRead;
- startupInfo.dwFlags = STARTF_USESTDHANDLES;
-
- OSString pathBuffer;
- LPCWSTR path = nullptr;
-
- if (commandLine.m_executableType == CommandLine::ExecutableType::Path)
- {
- StringBuilder cmd;
- StringEscapeUtil::appendMaybeQuoted(getEscapeHandler(), commandLine.m_executable.getUnownedSlice(), cmd);
-
- pathBuffer = cmd.toWString();
- path = pathBuffer.begin();
- }
-
- // Produce the command line string
- String cmdString = getCommandLineString(commandLine);
- OSString cmdStringBuffer = cmdString.toWString();
-
- // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa
- // `CreateProcess` requires write access to this, for some reason...
- BOOL success = CreateProcessW(
- path,
- (LPWSTR)cmdStringBuffer.begin(),
- nullptr,
- nullptr,
- true,
- CREATE_NO_WINDOW,
- nullptr, // TODO: allow specifying environment variables?
- nullptr,
- &startupInfo,
- &processInfo);
-
- if (!success)
- {
- DWORD err = GetLastError();
- SLANG_UNUSED(err);
-
- return SLANG_FAIL;
- }
-
- // close handles we are now done with
- CloseHandle(processInfo.hThread);
- }
-
- // Create a thread to read from the child's stdout.
- ThreadInfo stdOutThreadInfo;
- stdOutThreadInfo.file = childStdOutRead;
- WinHandle stdOutThread = CreateThread(nullptr, 0, &_readerThreadProc, (LPVOID)&stdOutThreadInfo, 0, nullptr);
-
- // Create a thread to read from the child's stderr.
- ThreadInfo stdErrThreadInfo;
- stdErrThreadInfo.file = childStdErrRead;
- WinHandle stdErrThread = CreateThread(nullptr, 0, &_readerThreadProc, (LPVOID)&stdErrThreadInfo, 0, nullptr);
-
- // wait for the process to exit
- // TODO: set a timeout as a safety measure...
- WaitForSingleObject(processInfo.hProcess, INFINITE);
-
- // get exit code for process
- // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
-
- DWORD childExitCode = 0;
- if (!GetExitCodeProcess(processInfo.hProcess, &childExitCode))
- {
- // TODO(JS): Do we want to close here? It seems plausible because just because reading the exit code failed, doesn't mean the handle is closed
- CloseHandle(processInfo.hProcess);
-
- return SLANG_FAIL;
- }
-
- // wait for the reader threads
- WaitForSingleObject(stdOutThread, INFINITE);
- WaitForSingleObject(stdErrThread, INFINITE);
-
- CloseHandle(processInfo.hProcess);
-
- outExecuteResult.standardOutput = stdOutThreadInfo.output;
- outExecuteResult.standardError = stdErrThreadInfo.output;
- outExecuteResult.resultCode = childExitCode;
-
- return SLANG_OK;
-}
-
-static uint64_t _getClockFrequency()
-{
- LARGE_INTEGER timerFrequency;
- QueryPerformanceFrequency(&timerFrequency);
- return timerFrequency.QuadPart;
-}
-
-static const uint64_t g_frequency = _getClockFrequency();
-
-/* static */uint64_t ProcessUtil::getClockFrequency()
-{
- return g_frequency;
-}
-
-/* static */uint64_t ProcessUtil::getClockTick()
-{
- LARGE_INTEGER counter;
- QueryPerformanceCounter(&counter);
- return counter.QuadPart;
-}
-
-}
diff --git a/source/core/windows/slang-win-process.cpp b/source/core/windows/slang-win-process.cpp
new file mode 100644
index 000000000..57eb209c3
--- /dev/null
+++ b/source/core/windows/slang-win-process.cpp
@@ -0,0 +1,495 @@
+// slang-win-process-util.cpp
+#include "../slang-process.h"
+
+#include "../slang-string.h"
+#include "../slang-string-escape-util.h"
+#include "../slang-string-util.h"
+#include "../slang-process-util.h"
+
+#include "../../../slang-com-helper.h"
+
+#ifdef _WIN32
+// Include Windows header in a way that minimized namespace pollution.
+// TODO: We could try to avoid including this at all, but it would
+// mean trying to hide certain struct layouts, which would add
+// more dynamic allocation.
+# define WIN32_LEAN_AND_MEAN
+# define NOMINMAX
+# include <Windows.h>
+# undef WIN32_LEAN_AND_MEAN
+# undef NOMINMAX
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef SLANG_RETURN_FAIL_ON_FALSE
+# define SLANG_RETURN_FAIL_ON_FALSE(x) if (!(x)) return SLANG_FAIL;
+#endif
+
+namespace Slang {
+
+// Has behavior very similar to unique_ptr - assignment is a move.
+class WinHandle
+{
+public:
+ /// Detach the encapsulated handle. Returns the handle (which now must be externally handled)
+ HANDLE detach() { HANDLE handle = m_handle; m_handle = nullptr; return handle; }
+
+ /// Return as a handle
+ operator HANDLE() const { return m_handle; }
+
+ /// Assign
+ void operator=(HANDLE handle) { setNull(); m_handle = handle; }
+ void operator=(WinHandle&& rhs) { HANDLE handle = m_handle; m_handle = rhs.m_handle; rhs.m_handle = handle; }
+
+ /// Get ready for writing
+ SLANG_FORCE_INLINE HANDLE* writeRef() { setNull(); return &m_handle; }
+ /// Get for read access
+ SLANG_FORCE_INLINE const HANDLE* readRef() const { return &m_handle; }
+
+ void setNull()
+ {
+ if (m_handle)
+ {
+ CloseHandle(m_handle);
+ m_handle = nullptr;
+ }
+ }
+ bool isNull() const { return m_handle == nullptr; }
+
+ /// Ctor
+ WinHandle(HANDLE handle = nullptr) :m_handle(handle) {}
+ WinHandle(WinHandle&& rhs) :m_handle(rhs.m_handle) { rhs.m_handle = nullptr; }
+
+ /// Dtor
+ ~WinHandle() { setNull(); }
+
+private:
+
+ WinHandle(const WinHandle&) = delete;
+ void operator=(const WinHandle& rhs) = delete;
+
+ HANDLE m_handle;
+};
+
+/* A simple Stream implementation of a File HANDLE (or Pipe). Note that currently does not allow getPosition/seek/atEnd */
+class WinPipeStream : public Stream
+{
+public:
+ typedef WinPipeStream ThisType;
+
+ // Stream
+ virtual Int64 getPosition() SLANG_OVERRIDE { return 0; }
+ virtual SlangResult seek(SeekOrigin origin, Int64 offset) SLANG_OVERRIDE { SLANG_UNUSED(origin); SLANG_UNUSED(offset); return SLANG_E_NOT_AVAILABLE; }
+ virtual SlangResult read(void* buffer, size_t length, size_t& outReadBytes) SLANG_OVERRIDE;
+ virtual SlangResult write(const void* buffer, size_t length) SLANG_OVERRIDE;
+ virtual bool isEnd() SLANG_OVERRIDE { return m_streamHandle.isNull(); }
+ virtual bool canRead() SLANG_OVERRIDE { return _has(FileAccess::Read) && !m_streamHandle.isNull(); }
+ virtual bool canWrite() SLANG_OVERRIDE { return _has(FileAccess::Write) && !m_streamHandle.isNull(); }
+ virtual void close() SLANG_OVERRIDE;
+ virtual SlangResult flush() SLANG_OVERRIDE;
+
+ WinPipeStream(HANDLE handle, FileAccess access, bool isOwned = true);
+
+ ~WinPipeStream() { close(); }
+
+protected:
+
+ bool _has(FileAccess access) const { return (Index(access) & Index(m_access)) != 0; }
+
+ SlangResult _updateState(BOOL res);
+
+ FileAccess m_access = FileAccess::None;
+ WinHandle m_streamHandle;
+ bool m_isOwned;
+};
+
+
+class WinProcess : public Process
+{
+public:
+
+ virtual bool isTerminated() SLANG_OVERRIDE;
+ virtual void waitForTermination() SLANG_OVERRIDE;
+
+ WinProcess(HANDLE handle, Stream* const* streams) :
+ m_processHandle(handle)
+ {
+ for (Index i = 0; i < Index(Process::StreamType::CountOf); ++i)
+ {
+ m_streams[i] = streams[i];
+ }
+ }
+
+protected:
+
+ void _hasTerminated();
+ WinHandle m_processHandle; ///< If not set the process has terminated
+};
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!! WinPipeStream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+WinPipeStream::WinPipeStream(HANDLE handle, FileAccess access, bool isOwned) :
+ m_streamHandle(handle),
+ m_access(access),
+ m_isOwned(isOwned)
+{
+ // It might be handy to get information about the handle
+ // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-getnamedpipeinfo
+
+ {
+ DWORD flags, outBufferSize, inBufferSize, maxInstances;
+ // It appears that by default windows pipe buffer size is 4k.
+ if (GetNamedPipeInfo(handle, &flags, &outBufferSize, &inBufferSize, &maxInstances))
+ {
+ }
+ }
+}
+
+SlangResult WinPipeStream::_updateState(BOOL res)
+{
+ if (res)
+ {
+ return SLANG_OK;
+ }
+ else
+ {
+ const auto err = GetLastError();
+
+ if (err == ERROR_BROKEN_PIPE)
+ {
+ m_streamHandle.setNull();
+ return SLANG_OK;
+ }
+
+ SLANG_UNUSED(err);
+ return SLANG_FAIL;
+ }
+}
+
+SlangResult WinPipeStream::read(void* buffer, size_t length, size_t& outReadBytes)
+{
+ outReadBytes = 0;
+ if (!_has(FileAccess::Read))
+ {
+ return SLANG_E_NOT_AVAILABLE;
+ }
+
+ if (m_streamHandle.isNull())
+ {
+ return SLANG_OK;
+ }
+
+ // Check if there is any data, so won't block
+ {
+ DWORD bytesRead = 0;
+ DWORD totalBytes = 0;
+ DWORD remainingBytes = 0;
+
+ // Works on anonymous pipes too
+ // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-peeknamedpipe
+
+ SLANG_RETURN_ON_FAIL(_updateState(PeekNamedPipe(m_streamHandle, nullptr, DWORD(0), &bytesRead, &totalBytes, &remainingBytes)));
+ // If there is nothing to read we are done
+ // If we don't do this ReadFile will *block* if there is nothing available
+ if (totalBytes == 0)
+ {
+ return SLANG_OK;
+ }
+ }
+
+ {
+ DWORD bytesRead = 0;
+ SLANG_RETURN_ON_FAIL(_updateState(ReadFile(m_streamHandle, buffer, DWORD(length), &bytesRead, nullptr)));
+
+ outReadBytes = size_t(bytesRead);
+ }
+
+ return SLANG_OK;
+}
+
+SlangResult WinPipeStream::write(const void* buffer, size_t length)
+{
+ if (!_has(FileAccess::Write))
+ {
+ return SLANG_E_NOT_AVAILABLE;
+ }
+
+ if (m_streamHandle.isNull())
+ {
+ // Writing to closed stream
+ return SLANG_FAIL;
+ }
+
+ DWORD numWritten = 0;
+ BOOL writeResult = WriteFile(m_streamHandle, buffer, DWORD(length), &numWritten, nullptr);
+
+ if (!writeResult || numWritten != length)
+ {
+ return SLANG_FAIL;
+ }
+
+ return SLANG_OK;
+}
+
+void WinPipeStream::close()
+{
+ if (!m_isOwned)
+ {
+ // If we don't own it just detach it
+ m_streamHandle.detach();
+ }
+ m_streamHandle.setNull();
+}
+
+SlangResult WinPipeStream::flush()
+{
+ if ((Index(m_access) & Index(FileAccess::Write)) == 0 || m_streamHandle.isNull())
+ {
+ return SLANG_E_NOT_AVAILABLE;
+ }
+
+ // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers
+
+ if (!FlushFileBuffers(m_streamHandle))
+ {
+ auto err = GetLastError();
+ SLANG_UNUSED(err);
+ }
+ return SLANG_OK;
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!! WinProcess !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+void WinProcess::_hasTerminated()
+{
+ if (!m_processHandle.isNull())
+ {
+ // get exit code for process
+ // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
+
+ DWORD childExitCode = 0;
+ if (GetExitCodeProcess(m_processHandle, &childExitCode))
+ {
+ m_returnValue = int32_t(childExitCode);
+ }
+ m_processHandle.setNull();
+ }
+}
+
+void WinProcess::waitForTermination()
+{
+ if (m_processHandle.isNull())
+ {
+ return;
+ }
+
+ // wait for the process to exit
+ // TODO: set a timeout as a safety measure...
+ WaitForSingleObject(m_processHandle, INFINITE);
+
+ _hasTerminated();
+}
+
+bool WinProcess::isTerminated()
+{
+ if (m_processHandle.isNull())
+ {
+ return true;
+ }
+
+ auto res = WaitForSingleObject(m_processHandle, 0);
+
+ if (res == WAIT_TIMEOUT)
+ {
+ return false;
+ }
+ _hasTerminated();
+ return true;
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+/* static */StringEscapeHandler* Process::getEscapeHandler()
+{
+ return StringEscapeUtil::getHandler(StringEscapeUtil::Style::Space);
+}
+
+/* static */UnownedStringSlice Process::getExecutableSuffix()
+{
+ return UnownedStringSlice::fromLiteral(".exe");
+}
+
+/* static */SlangResult Process::getStdStream(StreamType type, RefPtr<Stream>& out)
+{
+ switch (type)
+ {
+ case StreamType::StdIn:
+ {
+ const HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
+ out = new WinPipeStream(stdinHandle, FileAccess::Read, false);
+ return SLANG_OK;
+ }
+ }
+
+ return SLANG_FAIL;
+}
+
+/* static */SlangResult Process::create(const CommandLine& commandLine, Process::Flags flags, RefPtr<Process>& outProcess)
+{
+ WinHandle childStdOutRead;
+ WinHandle childStdErrRead;
+ WinHandle childStdInWrite;
+
+ WinHandle processHandle;
+ {
+ WinHandle childStdOutWrite;
+ WinHandle childStdErrWrite;
+ WinHandle childStdInRead;
+
+ SECURITY_ATTRIBUTES securityAttributes;
+ securityAttributes.nLength = sizeof(securityAttributes);
+ securityAttributes.lpSecurityDescriptor = nullptr;
+ securityAttributes.bInheritHandle = true;
+
+ // 0 means use the 'system default'
+ //const DWORD bufferSize = 64 * 1024;
+ const DWORD bufferSize = 0;
+
+ {
+ WinHandle childStdOutReadTmp;
+ WinHandle childStdErrReadTmp;
+ WinHandle childStdInWriteTmp;
+ // create stdout pipe for child process
+ SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdOutReadTmp.writeRef(), childStdOutWrite.writeRef(), &securityAttributes, bufferSize));
+ // create stderr pipe for child process
+ SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdErrReadTmp.writeRef(), childStdErrWrite.writeRef(), &securityAttributes, bufferSize));
+ // create stdin pipe for child process
+ SLANG_RETURN_FAIL_ON_FALSE(CreatePipe(childStdInRead.writeRef(), childStdInWriteTmp.writeRef(), &securityAttributes, bufferSize));
+
+ const HANDLE currentProcess = GetCurrentProcess();
+
+ // https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle
+
+ // create a non-inheritable duplicate of the stdout reader
+ SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdOutReadTmp, currentProcess, childStdOutRead.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS));
+ // create a non-inheritable duplicate of the stderr reader
+ SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdErrReadTmp, currentProcess, childStdErrRead.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS));
+ // create a non-inheritable duplicate of the stdin writer
+ SLANG_RETURN_FAIL_ON_FALSE(DuplicateHandle(currentProcess, childStdInWriteTmp, currentProcess, childStdInWrite.writeRef(), 0, FALSE, DUPLICATE_SAME_ACCESS));
+ }
+
+ // TODO: switch to proper wide-character versions of these...
+ STARTUPINFOW startupInfo;
+ ZeroMemory(&startupInfo, sizeof(startupInfo));
+ startupInfo.cb = sizeof(startupInfo);
+ startupInfo.hStdError = childStdErrWrite;
+ startupInfo.hStdOutput = childStdOutWrite;
+ startupInfo.hStdInput = childStdInRead;
+ startupInfo.dwFlags = STARTF_USESTDHANDLES;
+
+ OSString pathBuffer;
+ LPCWSTR path = nullptr;
+
+ if (commandLine.m_executableType == CommandLine::ExecutableType::Path)
+ {
+ StringBuilder cmd;
+ StringEscapeUtil::appendMaybeQuoted(getEscapeHandler(), commandLine.m_executable.getUnownedSlice(), cmd);
+
+ pathBuffer = cmd.toWString();
+ path = pathBuffer.begin();
+ }
+
+ // Produce the command line string
+ String cmdString = commandLine.toString();
+ OSString cmdStringBuffer = cmdString.toWString();
+
+ // Now we can actually get around to starting a process
+ PROCESS_INFORMATION processInfo;
+ ZeroMemory(&processInfo, sizeof(processInfo));
+
+ // https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
+
+ DWORD createFlags = CREATE_NO_WINDOW;
+
+ if (flags & Process::Flag::AttachDebugger)
+ {
+ createFlags |= CREATE_SUSPENDED;
+ }
+
+ // https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa
+ // `CreateProcess` requires write access to this, for some reason...
+ BOOL success = CreateProcessW(
+ path,
+ (LPWSTR)cmdStringBuffer.begin(),
+ nullptr,
+ nullptr,
+ true,
+ createFlags,
+ nullptr, // TODO: allow specifying environment variables?
+ nullptr,
+ &startupInfo,
+ &processInfo);
+
+ if (!success)
+ {
+ DWORD err = GetLastError();
+ SLANG_UNUSED(err);
+ return SLANG_FAIL;
+ }
+
+ if (flags & Process::Flag::AttachDebugger)
+ {
+ // Lets see if we can set up to debug
+ // https://docs.microsoft.com/en-us/windows/win32/debug/debugging-a-running-process
+
+ //DebugActiveProcess(processInfo.dwProcessId);
+
+ // Resume the thread
+ ResumeThread(processInfo.hThread);
+ }
+
+ // close handles we are now done with
+ CloseHandle(processInfo.hThread);
+
+ // Save the process handle
+ processHandle = processInfo.hProcess;
+ }
+
+ RefPtr<Stream> streams[Index(Process::StreamType::CountOf)];
+ streams[Index(Process::StreamType::ErrorOut)] = new WinPipeStream(childStdErrRead.detach(), FileAccess::Read);
+ streams[Index(Process::StreamType::StdOut)] = new WinPipeStream(childStdOutRead.detach(), FileAccess::Read);
+ streams[Index(Process::StreamType::StdIn)] = new WinPipeStream(childStdInWrite.detach(), FileAccess::Write);
+
+ outProcess = new WinProcess(processHandle.detach(), streams[0].readRef());
+ return SLANG_OK;
+}
+
+/* static */void Process::sleepCurrentThread(Index timeInMs)
+{
+ ::Sleep(DWORD(timeInMs));
+}
+
+static uint64_t _getClockFrequency()
+{
+ LARGE_INTEGER timerFrequency;
+ QueryPerformanceFrequency(&timerFrequency);
+ return timerFrequency.QuadPart;
+}
+
+static const uint64_t g_frequency = _getClockFrequency();
+
+/* static */uint64_t Process::getClockFrequency()
+{
+ return g_frequency;
+}
+
+/* static */uint64_t Process::getClockTick()
+{
+ LARGE_INTEGER counter;
+ QueryPerformanceCounter(&counter);
+ return counter.QuadPart;
+}
+
+} // namespace Slang
diff --git a/source/slang/slang-repro.cpp b/source/slang/slang-repro.cpp
index 67feebe43..d7ddd6aa8 100644
--- a/source/slang/slang-repro.cpp
+++ b/source/slang/slang-repro.cpp
@@ -1405,7 +1405,7 @@ static SlangResult _calcCommandLine(OffsetBase& base, ReproUtil::RequestState* r
{
CommandLine cmdLine;
_calcCommandLine(base, requestState, cmdLine);
- String text = ProcessUtil::getCommandLineString(cmdLine);
+ String text = cmdLine.toString();
builder << text << "\n";
}
@@ -1564,7 +1564,7 @@ static SlangResult _findFirstSourcePath(EndToEndCompileRequest* request, String&
for (int triesCount = 0; triesCount < maxTries; ++triesCount)
{
// We could include the count in some way perhaps, but for now let's just go with ticks
- auto tick = ProcessUtil::getClockTick();
+ auto tick = Process::getClockTick();
StringBuilder builder;
builder << sourceBaseName << "-" << tick << ".slang-repro";
diff --git a/tools/render-test/render-test-main.cpp b/tools/render-test/render-test-main.cpp
index 689794cef..a3cde1c0a 100644
--- a/tools/render-test/render-test-main.cpp
+++ b/tools/render-test/render-test-main.cpp
@@ -63,7 +63,7 @@ using namespace Slang;
static void _outputProfileTime(uint64_t startTicks, uint64_t endTicks)
{
WriterHelper out = StdWriters::getOut();
- double time = double(endTicks - startTicks) / ProcessUtil::getClockFrequency();
+ double time = double(endTicks - startTicks) / Process::getClockFrequency();
out.print("profile-time=%g\n", time);
}
@@ -991,7 +991,7 @@ Result RenderTestApp::update()
}
commandBuffer->close();
- m_startTicks = ProcessUtil::getClockTick();
+ m_startTicks = Process::getClockTick();
m_queue->executeCommandBuffer(commandBuffer);
m_queue->wait();
@@ -1030,7 +1030,7 @@ Result RenderTestApp::update()
// Note we don't do the same with screen rendering -> as that will do a lot of work, which may swamp any computation
// so can only really profile compute shaders at the moment
- const uint64_t endTicks = ProcessUtil::getClockTick();
+ const uint64_t endTicks = Process::getClockTick();
_outputProfileTime(m_startTicks, endTicks);
}
diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp
index ea2c322ad..e007d1978 100644
--- a/tools/slang-test/slang-test-main.cpp
+++ b/tools/slang-test/slang-test-main.cpp
@@ -536,7 +536,7 @@ Result spawnAndWaitExe(TestContext* context, const String& testPath, const Comma
if (options.shouldBeVerbose)
{
- String commandLine = ProcessUtil::getCommandLineString(cmdLine);
+ String commandLine = cmdLine.toString();
context->reporter->messageFormat(TestMessageType::Info, "%s\n", commandLine.begin());
}
@@ -570,8 +570,7 @@ Result spawnAndWaitSharedLibrary(TestContext* context, const String& testPath, c
testCmdLine.addArg(exeName);
testCmdLine.m_args.addRange(cmdLine.m_args);
- String testCmdLineString = ProcessUtil::getCommandLineString(testCmdLine);
- context->reporter->messageFormat(TestMessageType::Info, "%s\n", testCmdLineString.getBuffer());
+ context->reporter->messageFormat(TestMessageType::Info, "%s\n", testCmdLine.toString().getBuffer());
}
auto func = context->getInnerMainFunc(context->options.binDir, exeName);
@@ -636,14 +635,12 @@ Result spawnAndWaitProxy(TestContext* context, const String& testPath, const Com
// Make the first arg the name of the tool to invoke
cmdLine.m_args.insert(0, exeName);
-
- auto exePath = Path::combine(Path::getParentDirectory(inCmdLine.m_executable), String("test-proxy") + ProcessUtil::getExecutableSuffix());
- cmdLine.setExecutablePath(exePath);
+ cmdLine.setExecutable(context->exeDirectoryPath, "test-proxy");
const auto& options = context->options;
if (options.shouldBeVerbose)
{
- String commandLine = ProcessUtil::getCommandLineString(cmdLine);
+ String commandLine = cmdLine.toString();
context->reporter->messageFormat(TestMessageType::Info, "%s\n", commandLine.begin());
}
@@ -942,7 +939,7 @@ static RenderApiFlags _getAvailableRenderApiFlags(TestContext* context)
}
// Try starting up the device
CommandLine cmdLine;
- cmdLine.setExecutablePath(Path::combine(context->options.binDir, String("render-test") + ProcessUtil::getExecutableSuffix()));
+ cmdLine.setExecutable(context->options.binDir, "render-test");
_addRenderTestOptions(context->options, cmdLine);
// We just want to see if the device can be started up
cmdLine.addArg("-only-startup");
@@ -1086,7 +1083,7 @@ String findExpectedPath(const TestInput& input, const char* postFix)
static void _initSlangCompiler(TestContext* context, CommandLine& ioCmdLine)
{
- ioCmdLine.setExecutablePath(Path::combine(context->options.binDir, String("slangc") + ProcessUtil::getExecutableSuffix()));
+ ioCmdLine.setExecutable(context->options.binDir, "slangc");
if (context->options.verbosePaths)
{
@@ -1123,7 +1120,7 @@ static SlangResult _executeBinary(const UnownedStringSlice& hexDump, ExecuteResu
String fileName;
SLANG_RETURN_ON_FAIL(File::generateTemporary(UnownedStringSlice("slang-test"), fileName));
- fileName.append(ProcessUtil::getExecutableSuffix());
+ fileName.append(Process::getExecutableSuffix());
TemporaryFileSet temporaryFileSet;
temporaryFileSet.add(fileName);
@@ -1535,7 +1532,7 @@ TestResult runReflectionTest(TestContext* context, TestInput& input)
CommandLine cmdLine;
- cmdLine.setExecutablePath(Path::combine(options.binDir, String("slang-reflection-test") + ProcessUtil::getExecutableSuffix()));
+ cmdLine.setExecutable(options.binDir, "slang-reflection-test");
cmdLine.addArg(filePath);
for( auto arg : input.testOptions->args )
@@ -1820,7 +1817,7 @@ static TestResult runCPPCompilerExecute(TestContext* context, TestInput& input)
{
StringBuilder moduleExePath;
moduleExePath << modulePath;
- moduleExePath << ProcessUtil::getExecutableSuffix();
+ moduleExePath << Process::getExecutableSuffix();
File::remove(moduleExePath);
}
@@ -1855,7 +1852,7 @@ static TestResult runCPPCompilerExecute(TestContext* context, TestInput& input)
CommandLine cmdLine;
StringBuilder exePath;
- exePath << modulePath << ProcessUtil::getExecutableSuffix();
+ exePath << modulePath << Process::getExecutableSuffix();
cmdLine.setExecutablePath(exePath);
@@ -2321,7 +2318,7 @@ TestResult runPerformanceProfile(TestContext* context, TestInput& input)
CommandLine cmdLine;
- cmdLine.setExecutablePath(Path::combine(context->options.binDir, String("render-test") + ProcessUtil::getExecutableSuffix()));
+ cmdLine.setExecutable(context->options.binDir, "render-test");
cmdLine.addArg(input.filePath);
cmdLine.addArg("-performance-profile");
@@ -2474,7 +2471,7 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons
CommandLine cmdLine;
- cmdLine.setExecutablePath(Path::combine(context->options.binDir, String("render-test") + ProcessUtil::getExecutableSuffix()));
+ cmdLine.setExecutable(context->options.binDir, "render-test");
cmdLine.addArg(filePath999);
_addRenderTestOptions(context->options, cmdLine);
@@ -2582,7 +2579,7 @@ TestResult doRenderComparisonTestRun(TestContext* context, TestInput& input, cha
CommandLine cmdLine;
- cmdLine.setExecutablePath(Path::combine(context->options.binDir, String("render-test") + ProcessUtil::getExecutableSuffix()));
+ cmdLine.setExecutable(context->options.binDir, "render-test");
cmdLine.addArg(filePath);
_addRenderTestOptions(context->options, cmdLine);
@@ -3424,6 +3421,8 @@ static SlangResult runUnitTestModule(TestContext* context, TestOptions& testOpti
unitTestContext.slangGlobalSession = context->getSession();
unitTestContext.workDirectory = "";
unitTestContext.enabledApis = context->options.enabledApis;
+ unitTestContext.executableDirectory = context->exeDirectoryPath.getBuffer();
+
auto testCount = testModule->getTestCount();
for (SlangInt i = 0; i < testCount; i++)
diff --git a/tools/slang-unit-test/unit-test-process.cpp b/tools/slang-unit-test/unit-test-process.cpp
new file mode 100644
index 000000000..3dccf1eaf
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-process.cpp
@@ -0,0 +1,125 @@
+// unit-test-process.cpp
+
+#include "../../source/core/slang-string-util.h"
+#include "../../source/core/slang-process-util.h"
+#include "../../source/core/slang-io.h"
+
+#include "tools/unit-test/slang-unit-test.h"
+
+using namespace Slang;
+
+static SlangResult _countTest(UnitTestContext* context, Index size, Index crashIndex = -1)
+{
+ RefPtr<Process> process;
+
+ /* Here we are trying to test what happens if the server produces a large amount of data, and
+ we just wait for termination. Do we receive all of the data irrespective of how much there is? */
+
+ {
+ CommandLine cmdLine;
+
+ cmdLine.setExecutable(context->executableDirectory, "test-proxy");
+ cmdLine.addArg("count");
+
+ StringBuilder buf;
+ buf << size;
+
+ cmdLine.addArg(buf);
+
+ if (crashIndex >= 0)
+ {
+ buf.Clear();
+ buf << crashIndex;
+ cmdLine.addArg(buf);
+ }
+
+ SLANG_RETURN_ON_FAIL(Process::create(cmdLine, Process::Flag::AttachDebugger, process));
+ }
+
+ ExecuteResult exeRes;
+
+#if 0
+ /* It does block on ~4k of data which matches up with the buffer size, using this mechanism only works up to 4k on windows
+ which matches the default pipe buffer size */
+ process->waitForTermination();
+#endif
+
+ SLANG_RETURN_ON_FAIL(ProcessUtil::readUntilTermination(process, exeRes));
+
+ Index v = 0;
+ for (auto line : LineParser(exeRes.standardOutput.getUnownedSlice()))
+ {
+ if (line.getLength() == 0)
+ {
+ continue;
+ }
+
+ Index value;
+ StringUtil::parseInt(line, value);
+
+ if (value != v)
+ {
+ return SLANG_FAIL;
+ }
+
+ v++;
+ }
+
+ const Index endIndex = (crashIndex >= 0) ? (crashIndex + 1) : size;
+
+ return v == endIndex ? SLANG_OK : SLANG_FAIL;
+}
+
+static SlangResult _countTests(UnitTestContext* context)
+{
+ const Index sizes[] = { 1, 10, 1000, 1000, 10000, 100000 };
+ for (auto size : sizes)
+ {
+ SLANG_RETURN_ON_FAIL(_countTest(context, size));
+ SLANG_RETURN_ON_FAIL(_countTest(context, size, size / 2));
+ }
+
+ return SLANG_OK;
+}
+
+static SlangResult _reflectTest(UnitTestContext* context)
+{
+ RefPtr<Process> process;
+ {
+ CommandLine cmdLine;
+ cmdLine.setExecutable(context->executableDirectory, "test-proxy");
+ cmdLine.addArg("reflect");
+ SLANG_RETURN_ON_FAIL(Process::create(cmdLine, Process::Flag::AttachDebugger, process));
+ }
+
+ // Write a bunch of stuff to the stream
+ Stream* readStream = process->getStream(Process::StreamType::StdOut);
+ Stream* writeStream = process->getStream(Process::StreamType::StdIn);
+
+ List<Byte> readBuffer;
+
+ for (Index i = 0; i < 10000; i++)
+ {
+ SLANG_RETURN_ON_FAIL(StreamUtil::read(readStream, 0, readBuffer));
+
+ StringBuilder buf;
+
+ buf << i << " Hello " << i << "\n";
+ SLANG_RETURN_ON_FAIL(writeStream->write(buf.getBuffer(), buf.getLength()));
+ }
+
+ const char end[] = "end\n";
+ SLANG_RETURN_ON_FAIL(writeStream->write(end, SLANG_COUNT_OF(end) - 1));
+ writeStream->flush();
+
+ SLANG_RETURN_ON_FAIL(StreamUtil::readAll(readStream, 0, readBuffer));
+
+ return SLANG_OK;
+}
+
+SLANG_UNIT_TEST(CommandLineProcess)
+{
+ SLANG_CHECK(SLANG_SUCCEEDED(_countTests(unitTestContext)));
+
+ SLANG_CHECK(SLANG_SUCCEEDED(_reflectTest(unitTestContext)));
+}
diff --git a/tools/test-proxy/test-proxy-main.cpp b/tools/test-proxy/test-proxy-main.cpp
index 2b6c59047..70efc8907 100644
--- a/tools/test-proxy/test-proxy-main.cpp
+++ b/tools/test-proxy/test-proxy-main.cpp
@@ -12,6 +12,7 @@
#include "../../source/core/slang-io.h"
#include "../../source/core/slang-writer.h"
#include "../../source/core/slang-string-util.h"
+#include "../../source/core/slang-process-util.h"
#include "../../source/core/slang-shared-library.h"
@@ -111,10 +112,109 @@ static SlangResult _createSlangSession(const char* exePath, int argc, const char
return SLANG_OK;
}
-static SlangResult execute(int argc, const char* const* argv)
+static void _makeStdStreamsUnbuffered()
+{
+ // Make these streams unbuffered
+ setvbuf(stdout, nullptr, _IONBF, 0);
+ setvbuf(stderr, nullptr, _IONBF, 0);
+}
+
+static SlangResult _outputCount(int argc, const char* const* argv)
+{
+ if (argc < 3)
+ {
+ return SLANG_FAIL;
+ }
+ // Get the count
+ const Index count = StringToInt(argv[2]);
+
+ // If we want to crash
+ Index crashIndex = -1;
+ if (argc > 3)
+ {
+ // When we crash, we want to make sure everything is flushed...
+ _makeStdStreamsUnbuffered();
+ crashIndex = StringToInt(argv[3]);
+ }
+
+ FILE* fileOut = stdout;
+
+ StringBuilder buf;
+ for (Index i = 0; i < count; ++i)
+ {
+ buf.Clear();
+ buf << i << "\n";
+
+ fwrite(buf.getBuffer(), 1, buf.getLength(), fileOut);
+
+ if (i == crashIndex)
+ {
+ // Use to simulate a crash.
+ SLANG_BREAKPOINT(0);
+ throw;
+ }
+ }
+
+ // NOTE! There is no flush here, we want to see if everything works if we just stream out.
+ return SLANG_OK;
+}
+
+static SlangResult _outputReflect()
+{
+ // Read lines from std input.
+ // When hit line with just 'end', terminate
+
+ // Get in as Stream
+
+ RefPtr<Stream> stdinStream;
+ Process::getStdStream(Process::StreamType::StdIn, stdinStream);
+
+ FILE* fileOut = stdout;
+
+ List<Byte> buffer;
+
+ Index lineCount = 0;
+ Index startIndex = 0;
+
+ while (true)
+ {
+ SLANG_RETURN_ON_FAIL(StreamUtil::read(stdinStream, 0, buffer));
+
+ while (true)
+ {
+ UnownedStringSlice slice((const char*)buffer.begin() + startIndex, (const char*)buffer.end());
+ UnownedStringSlice line;
+
+ if (!StringUtil::extractLine(slice, line) || slice.begin() == nullptr)
+ {
+ break;
+ }
+
+ // Process the line
+ if (line == UnownedStringSlice::fromLiteral("end"))
+ {
+ return SLANG_OK;
+ }
+
+ // Write the text to the output stream
+ fwrite(line.begin(), 1, line.getLength(), fileOut);
+ fputc('\n', fileOut);
+
+ lineCount++;
+
+ // Move the start index forward
+ const Index newStartIndex = slice.begin() ? Index(slice.begin() - (const char*)buffer.getBuffer()) : buffer.getCount();
+ SLANG_ASSERT(newStartIndex > startIndex);
+ startIndex = newStartIndex;
+ }
+ }
+}
+
+static SlangResult execute(int argc, const char*const* argv)
{
typedef Slang::TestToolUtil::InnerMainFunc InnerMainFunc;
+
if (argc < 2)
{
return SLANG_FAIL;
@@ -125,6 +225,16 @@ static SlangResult execute(int argc, const char* const* argv)
// The 'exeName' of the tool.
const String toolName = argv[1];
+ if (toolName == "reflect")
+ {
+ return _outputReflect();
+ }
+
+ if (toolName == "count")
+ {
+ return _outputCount(argc, argv);
+ }
+
{
StringBuilder sharedLibToolBuilder;
sharedLibToolBuilder.append(toolName);
@@ -203,6 +313,7 @@ static SlangResult execute(int argc, const char* const* argv)
unitTestContext.slangGlobalSession = session;
unitTestContext.workDirectory = "";
unitTestContext.enabledApis = RenderApiFlags(enabledApis);
+ unitTestContext.executableDirectory = exePath.getBuffer();
auto testCount = testModule->getTestCount();
SLANG_ASSERT(testIndex >= 0 && testIndex < testCount);
diff --git a/tools/unit-test/slang-unit-test.h b/tools/unit-test/slang-unit-test.h
index 9e437f072..035f67e33 100644
--- a/tools/unit-test/slang-unit-test.h
+++ b/tools/unit-test/slang-unit-test.h
@@ -37,6 +37,7 @@ struct UnitTestContext
{
slang::IGlobalSession* slangGlobalSession;
const char* workDirectory;
+ const char* executableDirectory;
Slang::RenderApiFlags enabledApis;
};