test/io_uring: Avoid some tests on older kernels

Registering files is buggy on old kernels and breaks the test suite
periodically.

Change-Id: I5a99d3317a1e2177ab5e56e03019c9c3e0160a77
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/test/io_uring.cpp b/test/io_uring.cpp
index 755ecf8..30516c8 100644
--- a/test/io_uring.cpp
+++ b/test/io_uring.cpp
@@ -1,10 +1,13 @@
 #include <poll.h>
 
 #include <array>
+#include <charconv>
 #include <chrono>
 #include <optional>
 #include <stdplus/io_uring.hpp>
+#include <stdplus/util/cexec.hpp>
 #include <string_view>
+#include <sys/utsname.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -14,6 +17,52 @@
 
 using testing::_;
 
+static std::string_view extractVerNum(std::string_view& str)
+{
+    auto ret = str.substr(0, str.find('.'));
+    str.remove_prefix(ret.size() + (ret.size() == str.size() ? 0 : 1));
+    return ret;
+}
+
+template <typename T>
+static T intFromStr(std::string_view str)
+{
+    T ret;
+    auto res = std::from_chars(str.data(), str.data() + str.size(), ret);
+    if (res.ec != std::errc{} || res.ptr != str.data() + str.size())
+    {
+        throw std::invalid_argument("Bad kernel version");
+    }
+    return ret;
+}
+
+static bool isKernelSafe(std::string_view ver, uint8_t smajor, uint8_t sminor)
+{
+    auto major = intFromStr<uint8_t>(extractVerNum(ver));
+    auto minor = intFromStr<uint8_t>(extractVerNum(ver));
+    return major > smajor || (major == smajor && minor >= sminor);
+}
+
+TEST(KernelInfo, VersionSafe)
+{
+    EXPECT_THROW(isKernelSafe("a", 5, 10), std::invalid_argument);
+    EXPECT_THROW(isKernelSafe("3a.3b.c", 5, 10), std::invalid_argument);
+    EXPECT_FALSE(isKernelSafe("4.11.20-nfd", 5, 10));
+    EXPECT_FALSE(isKernelSafe("4.11.20", 5, 10));
+    EXPECT_FALSE(isKernelSafe("4.11", 5, 10));
+    EXPECT_FALSE(isKernelSafe("5.9.0", 5, 10));
+    EXPECT_TRUE(isKernelSafe("5.10.1", 5, 10));
+    EXPECT_TRUE(isKernelSafe("6.0.0", 5, 10));
+    EXPECT_TRUE(isKernelSafe("6.0.0-abc", 5, 10));
+}
+
+static bool checkKernelSafe(uint8_t smajor, uint8_t sminor)
+{
+    utsname uts;
+    CHECK_ERRNO(uname(&uts), "uname");
+    return isKernelSafe(uts.release, smajor, sminor);
+}
+
 TEST(Convert, ChronoToKTS)
 {
     const auto ns = 700;
@@ -175,6 +224,12 @@
 
 TEST_F(IoUringTest, RegisterFiles)
 {
+    // Earlier kernels had buggy support for registered files
+    if (!checkKernelSafe(5, 10))
+    {
+        GTEST_SKIP();
+    }
+
     std::optional<IoUring::FileHandle> fh;
 
     // Slots are always allocated linearly and re-used if invalidated