diff options
author | Jenkins nedprod CI <jenkins-nedprod@europe5.nedproductions.biz> | 2020-07-04 02:00:11 +0300 |
---|---|---|
committer | Jenkins nedprod CI <jenkins-nedprod@europe5.nedproductions.biz> | 2020-07-04 02:00:11 +0300 |
commit | 1102fa19c1c8ec4f16273647496a663d9f5e4275 (patch) | |
tree | 98db0237cf00a42cd7e9b2893413480def2418c4 | |
parent | 7183d2db61625b516d8c833e218bfcce8184c27c (diff) | |
parent | ad5c79a489ea8176d58897dbc1444fa728c6d428 (diff) |
Merged from develop branch as CDash reports all green
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | include/llfio/revision.hpp | 6 | ||||
-rw-r--r-- | include/llfio/v2.0/algorithm/clone.hpp | 94 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/posix/file_handle.ipp | 21 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/posix/fs_handle.ipp | 37 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/reduce.ipp | 5 | ||||
-rw-r--r-- | test/tests/clone_extents.cpp | 11 | ||||
-rw-r--r-- | test/tests/path_view.cpp | 7 | ||||
-rw-r--r-- | test/tests/process_handle.cpp | 28 |
9 files changed, 117 insertions, 94 deletions
diff --git a/.travis.yml b/.travis.yml index ab92e794..b1de8163 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,8 @@ notifications: addons: apt: +# sources: +# - sourceline: "ppa:ubuntu-toolchain-r/test" packages: - g++ - clang diff --git a/include/llfio/revision.hpp b/include/llfio/revision.hpp index 299a9ed5..bafbaa31 100644 --- a/include/llfio/revision.hpp +++ b/include/llfio/revision.hpp @@ -1,4 +1,4 @@ // Note the second line of this file must ALWAYS be the git SHA, third line ALWAYS the git SHA update time -#define LLFIO_PREVIOUS_COMMIT_REF 663b68847af5fd8da5265e5f893bd865e401d0a0 -#define LLFIO_PREVIOUS_COMMIT_DATE "2020-06-22 09:37:28 +00:00" -#define LLFIO_PREVIOUS_COMMIT_UNIQUE 663b6884 +#define LLFIO_PREVIOUS_COMMIT_REF 07068de54ae723c8e4e3d9c91fde97ee378ccad2 +#define LLFIO_PREVIOUS_COMMIT_DATE "2020-07-03 19:01:39 +00:00" +#define LLFIO_PREVIOUS_COMMIT_UNIQUE 07068de5 diff --git a/include/llfio/v2.0/algorithm/clone.hpp b/include/llfio/v2.0/algorithm/clone.hpp index 90b86e4c..fbfade35 100644 --- a/include/llfio/v2.0/algorithm/clone.hpp +++ b/include/llfio/v2.0/algorithm/clone.hpp @@ -76,7 +76,6 @@ namespace algorithm bool preserve_timestamps = true, bool force_copy_now = false, file_handle::creation creation = file_handle::creation::always_new, deadline d = {}) noexcept; - #if 0 #ifdef _MSC_VER #pragma warning(push) @@ -84,96 +83,45 @@ namespace algorithm #endif /*! \brief A visitor for the filesystem traversal and cloning algorithm. - Note that at any time, returning a failure causes `clone()` to exit as soon - as possible with the same failure. + Note that at any time, returning a failure causes `copy_directory_hierarchy()` + or `clone_or_copy()` to exit as soon as possible with the same failure. You can override the members here inherited from `traverse_visitor`, however note - that `clone()` is entirely implemented using `traverse()`, so not calling the - implementations here will affect operation. + that `copy_directory_hierarchy()` or `clone_or_copy()` is entirely implemented + using `traverse()`, so not calling the implementations here will affect operation. */ - struct LLFIO_DECL clone_copy_link_visitor : public traverse_visitor + struct LLFIO_DECL clone_copy_link_symlink_visitor : public traverse_visitor { + enum op_t + { + none = 0,//!< File content is to be ignored, directories only + clone, //!< File content is to be extents cloned when possible + copy, //!< File content is to copied now into its destination + link, //!< File content is to be hard linked into its destination + symlink //! File content is to be symlinked into its destination + } op; + bool follow_symlinks{false}; std::chrono::steady_clock::duration timeout{std::chrono::seconds(10)}; std::chrono::steady_clock::time_point begin; //! Constructs an instance with the default timeout of ten seconds. - constexpr clone_copy_link_visitor() {} + constexpr clone_copy_link_symlink_visitor(op_t _op, bool _follow_symlinks = false) : op(_op), follow_symlinks(_follow_symlinks) {} //! Constructs an instance with the specified timeout. - constexpr explicit clone_copy_link_visitor(std::chrono::steady_clock::duration _timeout) - : timeout(_timeout) - { - } - - //! This override ignores failures to traverse into the directory, and tries renaming the item into the base directory. - virtual result<directory_handle> directory_open_failed(void *data, result<void>::error_type &&error, const directory_handle &dirh, path_view leaf, size_t depth) noexcept override; - //! This override invokes deletion of all non-directory items. If there are no directory items, also deletes the directory. - virtual result<void> post_enumeration(void *data, const directory_handle &dirh, directory_handle::buffers_type &contents, size_t depth) noexcept override; - - /*! \brief Called when the unlink of a file entry failed. The default - implementation attempts to rename the entry into the base directory. - If your reimplementation achieves the unlink, return true. - - \note May be called from multiple kernel threads concurrently. - */ - virtual result<bool> unlink_failed(void *data, result<void>::error_type &&error, const directory_handle &dirh, directory_entry &entry, size_t depth) noexcept; - /*! \brief Called when the rename of a file entry into the base directory - failed. The default implementation ignores the failure. If your - reimplementation achieves the rename, return true. - - \note May be called from multiple kernel threads concurrently. - */ - virtual result<bool> rename_failed(void *data, result<void>::error_type &&error, const directory_handle &dirh, directory_entry &entry, size_t depth) noexcept + constexpr explicit clone_copy_link_symlink_visitor(op_t _op, std::chrono::steady_clock::duration _timeout, bool _follow_symlinks = false) + : op(_op), follow_symlinks(_follow_symlinks), timeout(_timeout) { - (void) data; - (void) error; - (void) dirh; - (void) entry; - (void) depth; - return false; } - /*! \brief Called when we have performed a single full round of reduction. - \note Always called from the original kernel thread. + /*! \brief This override creates directories in the destination for + every directory in the source, and optionally clones/copies/links/symlinks + any file content, optionally dereferencing or not dereferencing symlinks. */ - virtual result<void> reduction_round(void *data, size_t round_completed, size_t items_unlinked, size_t items_remaining) noexcept - { - (void) data; - (void) round_completed; - (void) items_unlinked; - if(items_remaining > 0) - { - if(begin == std::chrono::steady_clock::time_point()) - { - begin = std::chrono::steady_clock::now(); - } - else if((std::chrono::steady_clock::now() - begin) > timeout) - { - return errc::timed_out; - } - } - return success(); - } + virtual result<void> post_enumeration(void *data, const directory_handle &dirh, directory_handle::buffers_type &contents, size_t depth) noexcept override; }; #ifdef _MSC_VER #pragma warning(pop) #endif - /*! \brief Copy the directory hierarchy identified by `srcdir` to `destdir`. - - This algorithm firstly traverses the source directory tree to calculate the filesystem - blocks which would be used by a copy of all the directories. If insufficient disc space - remains on the destination volume, the operation exits with an error code comparing equal to - `errc::no_space_on_device`. - - If there is sufficient disc space, the directory hierarchy -- without any files -- is - replicated exactly. Timestamps are NOT replicated (any subsequent newly added files - would change the timestamps in any case). - - You should review the documentation for `algorithm::traverse()`, as this algorithm is - entirely implemented using that algorithm. - */ - LLFIO_HEADERS_ONLY_FUNC_SPEC result<size_t> copy_hierarchy(const directory_handle &srcdir, directory_handle &destdir, clone_copy_link_visitor *visitor = nullptr, size_t threads = 0, bool force_slow_path = false) noexcept; - /*! \brief Clone or copy the extents of the filesystem entity identified by `srcdir` and `srcleaf`, and everything therein, to `destdir` named `srcleaf` or `destleaf`. diff --git a/include/llfio/v2.0/detail/impl/posix/file_handle.ipp b/include/llfio/v2.0/detail/impl/posix/file_handle.ipp index ef41f330..bdc57f25 100644 --- a/include/llfio/v2.0/detail/impl/posix/file_handle.ipp +++ b/include/llfio/v2.0/detail/impl/posix/file_handle.ipp @@ -32,6 +32,8 @@ Distributed under the Boost Software License, Version 1.0. #include <sys/uio.h> #endif +//#include <iostream> + LLFIO_V2_NAMESPACE_BEGIN result<file_handle> file_handle::file(const path_handle &base, file_handle::path_view_type path, file_handle::mode _mode, file_handle::creation _creation, @@ -561,8 +563,12 @@ result<file_handle::extent_pair> file_handle::clone_extents_to(file_handle::exte todo.reserve(8); // Firstly fill todo with the list of allocated and non-allocated extents { +#if defined(SEEK_DATA) && !defined(__APPLE__) + /* Apple's SEEK_HOLE implementation is basically unusable. I discovered this the + hard way :). There is lots of useful detail as to why at + https://lists.gnu.org/archive/html/bug-gnulib/2018-09/msg00054.html + */ extent_type start = 0, end = 0; -#ifdef SEEK_DATA for(;;) { #ifdef __linux__ @@ -570,6 +576,7 @@ result<file_handle::extent_pair> file_handle::clone_extents_to(file_handle::exte #else start = lseek(_v.fd, end, SEEK_DATA); #endif + // std::cout << "SEEK_DATA from " << end << " finds " << start << std::endl; if(static_cast<extent_type>(-1) == start) { if(ENXIO == errno) @@ -588,8 +595,10 @@ result<file_handle::extent_pair> file_handle::clone_extents_to(file_handle::exte { break; // done } + // std::cout << "There are deallocated extents between " << end << " " << (start - end) << " bytes" << std::endl; // hole goes from end to start. end is inclusive, start is exclusive. - if((end >= extent.offset && end < extent.offset + extent.length) || (start > extent.offset && start <= extent.offset + extent.length)) + if((end <= extent.offset && start >= extent.offset + extent.length) || (end >= extent.offset && end < extent.offset + extent.length) || + (start > extent.offset && start <= extent.offset + extent.length)) { const auto clampedstart = std::max(end, extent.offset); const auto clampedend = std::min(start, extent.offset + extent.length); @@ -604,6 +613,7 @@ result<file_handle::extent_pair> file_handle::clone_extents_to(file_handle::exte #else end = lseek(_v.fd, start, SEEK_HOLE); #endif + // std::cout << "SEEK_HOLE from " << start << " finds " << end << std::endl; if(static_cast<extent_type>(-1) == end) { if(ENXIO == errno) @@ -618,14 +628,18 @@ result<file_handle::extent_pair> file_handle::clone_extents_to(file_handle::exte break; } } + // std::cout << "There are allocated extents between " << start << " " << (end - start) << " bytes" << std::endl; // allocated goes from start to end. start is inclusive, end is exclusive. - if((start >= extent.offset && start < extent.offset + extent.length) || (end > extent.offset && end <= extent.offset + extent.length)) + if((start <= extent.offset && end >= extent.offset + extent.length) || (start >= extent.offset && start < extent.offset + extent.length) || + (end > extent.offset && end <= extent.offset + extent.length)) { const auto clampedstart = std::max(start, extent.offset); const auto clampedend = std::min(end, extent.offset + extent.length); todo.push_back(workitem{extent_pair(clampedstart, clampedend - clampedstart), workitem::clone_extents}); } } +#else + todo.push_back(workitem{{extent.offset, extent.length}, workitem::clone_extents}); #endif } // Handle there being insufficient source to fill dest @@ -871,7 +885,6 @@ result<file_handle::extent_pair> file_handle::clone_extents_to(file_handle::exte } done = true; } - assert(done); dest_length = destoffset + extent.length; truncate_back_on_failure = false; LLFIO_DEADLINE_TO_TIMEOUT_LOOP(d); diff --git a/include/llfio/v2.0/detail/impl/posix/fs_handle.ipp b/include/llfio/v2.0/detail/impl/posix/fs_handle.ipp index 33fc6c2e..5e936068 100644 --- a/include/llfio/v2.0/detail/impl/posix/fs_handle.ipp +++ b/include/llfio/v2.0/detail/impl/posix/fs_handle.ipp @@ -63,11 +63,44 @@ namespace detail for(;;) { // Get current path for handle and open its containing dir - OUTCOME_TRY(auto &&_currentpath, h.current_path()); + OUTCOME_TRY(auto _currentpath, h.current_path()); // If current path is empty, it's been deleted if(_currentpath.empty()) { - return errc::no_such_file_or_directory; +#if defined(__linux__) && 0 // not the cause of the Travis failure + if(h.is_directory()) + { + /* Docker's mechanism for protecting /proc on Linux is bugged. For files, + we simply must give up. For directories, we can hack a workaround. + */ + fprintf(stderr, "llfio: Docker bug failure to retrieve parent path workaround is being employed\n"); + auto tempname = utils::random_string(32); + int tffd = ::openat(h.native_handle().fd, tempname.c_str(), O_RDWR | O_CLOEXEC | O_CREAT | O_EXCL, 0x1b0 /*660*/); + if(tffd >= 0) + { + auto untffd = make_scope_exit([&]() noexcept { + ::unlinkat(h.native_handle().fd, tempname.c_str(), 0); + ::close(tffd); + }); + std::string ret(32769, '\0'); + auto *out = const_cast<char *>(ret.data()); + // Linux keeps a symlink at /proc/self/fd/n + char in[64]; + snprintf(in, sizeof(in), "/proc/self/fd/%d", tffd); + ssize_t len; + if((len = readlink(in, out, 32768)) >= 0) + { + ret.resize(len); + _currentpath = std::move(ret); + _currentpath = _currentpath.parent_path(); + } + } + } + if(_currentpath.empty()) +#endif + { + return errc::no_such_file_or_directory; + } } // Split the path into root and leafname path_view currentpath(_currentpath); diff --git a/include/llfio/v2.0/detail/impl/reduce.ipp b/include/llfio/v2.0/detail/impl/reduce.ipp index 8251fb8b..7612e7ea 100644 --- a/include/llfio/v2.0/detail/impl/reduce.ipp +++ b/include/llfio/v2.0/detail/impl/reduce.ipp @@ -240,8 +240,9 @@ namespace algorithm { return success(); } + return posix_error(); } - return posix_error(); + return success(); #endif } struct reduction_state @@ -289,7 +290,7 @@ namespace algorithm if(r) { state->items_removed.fetch_add(1, std::memory_order_relaxed); - entry = {}; // prevent traversal + entry.stat = stat_t(nullptr); // prevent traversal } else { diff --git a/test/tests/clone_extents.cpp b/test/tests/clone_extents.cpp index 652e719e..315b3427 100644 --- a/test/tests/clone_extents.cpp +++ b/test/tests/clone_extents.cpp @@ -35,15 +35,15 @@ Distributed under the Boost Software License, Version 1.0. static inline void TestCloneExtents() { - static constexpr int DURATION = 20; + static constexpr int DURATION = 30; static constexpr size_t max_file_extent = (size_t) 100 * 1024 * 1024; namespace llfio = LLFIO_V2_NAMESPACE; using QUICKCPPLIB_NAMESPACE::algorithm::small_prng::small_prng; static const auto &tempdirh = llfio::path_discovery::storage_backed_temporary_files_directory(); - small_prng rand; auto begin = std::chrono::steady_clock::now(); for(size_t round = 0; std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - begin).count() < DURATION; round++) { + small_prng rand((uint32_t) round); struct handle_t { std::vector<llfio::file_handle::extent_pair> extents_written; @@ -71,7 +71,7 @@ static inline void TestCloneExtents() } auto size = rand() % std::min(h.maximum_extent - offset, h.maximum_extent / 256); llfio::byte buffer[65536]; - memset(&buffer, c, sizeof(buffer)); + memset(buffer, c, sizeof(buffer)); h.extents_written.push_back({offset, size}); for(unsigned n = 0; n < size; n += sizeof(buffer)) { @@ -196,7 +196,10 @@ static inline void TestCloneOrCopyFileWhole() dest_stat.fill(destfh).value(); std::cout << "Source file has " << src_stat.st_blocks << " blocks allocated. Destination file has " << dest_stat.st_blocks << " blocks allocated." << std::endl; - BOOST_CHECK(abs((long) src_stat.st_blocks - (long) dest_stat.st_blocks) < ((long) src_stat.st_blocks / 8)); +#ifndef __APPLE__ + // Mac OS has a broken extent enumeration API, so we just do straight copies on it + BOOST_CHECK(abs((long) src_stat.st_blocks - (long) dest_stat.st_blocks) < ((long) src_stat.st_blocks / 4)); +#endif for(size_t n = 0; n < maximum_extent; n++) { diff --git a/test/tests/path_view.cpp b/test/tests/path_view.cpp index a0488b5e..f8f40da2 100644 --- a/test/tests/path_view.cpp +++ b/test/tests/path_view.cpp @@ -188,6 +188,13 @@ static inline void TestPathView() BOOST_CHECK(llfio::path_view(L"\\\\.\\niall").is_absolute()); // On Windows this is relative, on POSIX it is absolute BOOST_CHECK(llfio::path_view("/niall").is_relative()); + +// This fails on VS2017 for no obvious reason +#if _MSC_VER >= 1920 + llfio:: + path_view_component longthing(LR"(Path=C:\Program Files\Docker\Docker\Resources\bin;C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin;C:\Perl\site\bin;C:\Perl\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files\7-Zip;C:\Tools\GitVersion;C:\Tools\NuGet;C:\Program Files\Microsoft\Web Platform Installer\;C:\Tools\PsTools;C:\Program Files\Git LFS;C:\Program Files\Mercurial\;C:\Program Files (x86)\Subversion\bin;C:\Tools\WebDriver;C:\Tools\Coverity\bin;C:\Tools\MSpec;C:\Tools\NUnit\bin;C:\Tools\NUnit3;C:\Tools\xUnit;C:\Program Files\nodejs;C:\Program Files (x86)\iojs;C:\Program Files\iojs;C:\Program Files\Microsoft SQL Server\120\Tools\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\110\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\120\Tools\Binn\;C:\Program Files\Microsoft SQL Server\120\DTS\Binn\;C:\Program Files (x86)\Microsoft SQL Server\120\Tools\Binn\ManagementStudio\;C:\Program Files (x86)\Microsoft SQL Server\120\DTS\Binn\;C:\Program Files (x86)\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\130\DTS\Binn\;C:\Program Files\Microsoft SQL Server\130\DTS\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn\;C:\Ruby193\bin;C:\go\bin;C:\Program Files\Java\jdk1.8.0\bin;C:\Program Files (x86)\Apache\Maven\bin;C:\Python27;C:\Python27\Scripts;C:\Program Files (x86)\CMake\bin;C:\Tools\curl\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin;C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\130;C:\Program Files\dotnet\;C:\Tools\vcpkg;C:\Program Files (x86)\dotnet\;C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\;C:\Program Files\Microsoft SQL Server\140\Tools\Binn\;C:\Program Files\Microsoft SQL Server\140\DTS\Binn\;C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\Extensions\TestPlatform;C:\Program Files (x86)\Microsoft SQL Server\110\DTS\Binn\;C:\Program Files (x86)\Microsoft SQL Server\140\DTS\Binn\;C:\Program Files\erl9.2\bin;C:\Program Files (x86)\NSIS;C:\Tools\Octopus;C:\Program Files\Microsoft Service Fabric\bin\Fabric\Fabric.Code;C:\Program Files\Microsoft SDKs\Service Fabric\Tools\ServiceFabricLocalClusterManager;C:\Program Files\Docker\Docker\resources;C:\Program Files\LLVM\bin;C:\Users\appveyor\AppData\Roaming\npm;C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\;C:\Program Files\PowerShell\6\;C:\Program Files (x86)\nodejs\;C:\Program Files\Git\cmd;C:\Program Files\Git\usr\bin;C:\Program Files\Meson\;C:\ProgramData\chocolatey\bin;C:\Program Files\Amazon\AWSCLI\;C:\Program Files (x86)\Yarn\bin\;C:\Users\appveyor\AppData\Local\Microsoft\WindowsApps;C:\Users\appveyor\.dotnet\tools;C:\Users\appveyor\AppData\Roaming\npm;C:\Users\appveyor\AppData\Local\Yarn\bin;C:\Program Files\AppVeyor\BuildAgent\)"); + std::cout << "A very long path component is " << longthing << std::endl; +#endif #else BOOST_CHECK(llfio::path_view("/niall").is_absolute()); #endif diff --git a/test/tests/process_handle.cpp b/test/tests/process_handle.cpp index 444aafc2..5aff5eb1 100644 --- a/test/tests/process_handle.cpp +++ b/test/tests/process_handle.cpp @@ -28,7 +28,8 @@ Distributed under the Boost Software License, Version 1.0. #include "../test_kernel_decl.hpp" -static inline void TestProcessHandle(bool with_redirection) { +static inline void TestProcessHandle(bool with_redirection) +{ namespace llfio = LLFIO_V2_NAMESPACE; std::vector<llfio::process_handle> children; auto &self = llfio::process_handle::current(); @@ -36,9 +37,22 @@ static inline void TestProcessHandle(bool with_redirection) { std::cout << "My process executable's path is " << myexepath << std::endl; auto myenv = self.environment(); std::cout << "My process environment contains:"; - for(auto &i : *myenv) + if(myenv) { - std::cout << "\n " << i; + for(auto &i : *myenv) + { + if(visit(i, [](auto sv) -> bool { + if(sv.size() >= 512) + return false; + using _string_view = std::decay_t<decltype(sv)>; + _string_view a((const typename _string_view::value_type *) "JENKINS_NEDPROD_PASSWORD"); + _string_view b((const typename _string_view::value_type *) L"JENKINS_NEDPROD_PASSWORD"); + return (sv.npos == sv.find(a)) && (sv.npos == sv.find(b)); + })) + { + std::cout << "\n " << i; + } + } } std::cout << "\n" << std::endl; llfio::process_handle::flag flags = llfio::process_handle::flag::wait_on_close; @@ -46,7 +60,7 @@ static inline void TestProcessHandle(bool with_redirection) { { flags |= llfio::process_handle::flag::no_redirect; } - for(size_t n=0; n<4; n++) + for(size_t n = 0; n < 4; n++) { char buffer[64]; sprintf(buffer, "--testchild,%u", (unsigned) n); @@ -72,8 +86,10 @@ static inline void TestProcessHandle(bool with_redirection) { } } -KERNELTEST_TEST_KERNEL(integration, llfio, process_handle, no_redirect, "Tests that llfio::process_handle without redirection works as expected", TestProcessHandle(false)) -KERNELTEST_TEST_KERNEL(integration, llfio, process_handle, redirect, "Tests that llfio::process_handle with redirection works as expected", TestProcessHandle(true)) +KERNELTEST_TEST_KERNEL(integration, llfio, process_handle, no_redirect, "Tests that llfio::process_handle without redirection works as expected", + TestProcessHandle(false)) +KERNELTEST_TEST_KERNEL(integration, llfio, process_handle, redirect, "Tests that llfio::process_handle with redirection works as expected", + TestProcessHandle(true)) int main(int argc, char *argv[]) { |