From c1387013d2be0ac417368ff173578a6ff0690260 Mon Sep 17 00:00:00 2001
From: Florian Fischer <florian.fischer@muhq.space>
Date: Tue, 14 Sep 2021 16:11:56 +0200
Subject: [PATCH] [lib/LinuxVersion] multiple fixes

* remove fancy but broken static LinuxVersion object.
  This means each usage of a EMPER_LINUX_* macro results in a syscall.
  But I think this is worth it when the result is a sound program.
  Users of those macros should store the result of the comparison anyway.
* fix that we skipped parsing the last part of a version string
* add a test for LinuxVersion
* IO_MUST_INVALIDATE_BROKEN_CHAIN can still be const
---
 emper/Emper.cpp                |  2 +-
 emper/Emper.hpp                |  2 +-
 emper/lib/LinuxVersion.cpp     | 22 ++++++----
 emper/lib/LinuxVersion.hpp     | 22 +++++-----
 tests/lib/LinuxVersionTest.cpp | 75 ++++++++++++++++++++++++++++++++++
 tests/lib/meson.build          |  8 ++++
 6 files changed, 109 insertions(+), 22 deletions(-)
 create mode 100644 tests/lib/LinuxVersionTest.cpp

diff --git a/emper/Emper.cpp b/emper/Emper.cpp
index c9f6be84..72a6d51c 100644
--- a/emper/Emper.cpp
+++ b/emper/Emper.cpp
@@ -45,7 +45,7 @@ void async(const Fiber::fiber_fun0_t& function, workeraffinity_t* affinity) {
 
 namespace emper {
 
-bool IO_MUST_INVALIDATE_BROKEN_CHAIN = EMPER_LINUX_LT("5.15.0");
+const bool IO_MUST_INVALIDATE_BROKEN_CHAIN = EMPER_LINUX_LT("5.15.0");
 
 auto getFullVersion() -> std::string { return EMPER_FULL_VERSION; }
 
diff --git a/emper/Emper.hpp b/emper/Emper.hpp
index 78736f3c..13cb2c00 100644
--- a/emper/Emper.hpp
+++ b/emper/Emper.hpp
@@ -138,7 +138,7 @@ static const bool IO_URING_SQPOLL =
 // Emper.cpp object.
 // This also has the advantage that the probability we crash because printing
 // warnings during the comparison use not yet initialized components is reduced.
-extern bool IO_MUST_INVALIDATE_BROKEN_CHAIN;
+extern const bool IO_MUST_INVALIDATE_BROKEN_CHAIN;
 
 static const bool IO_URING_SHARED_WQ =
 #ifdef EMPER_IO_URING_SHARED_WQ
diff --git a/emper/lib/LinuxVersion.cpp b/emper/lib/LinuxVersion.cpp
index f37e5f36..f268d050 100644
--- a/emper/lib/LinuxVersion.cpp
+++ b/emper/lib/LinuxVersion.cpp
@@ -33,19 +33,22 @@ static auto checked_strtol(const std::string& s) -> long {
 
 namespace emper::lib {
 
-LinuxVersion LinuxVersion::linuxVersion;
-
 // Returns 1 if s is smaller, -1 if this is smaller, 0 if equal
 auto LinuxVersion::compare(const std::string& s) -> int {
+	size_t dot_pos, dot_pos2;
 	size_t last_dot_pos = 0, last_dot_pos2 = 0;
 
+	bool was_last = false;
 	for (;;) {
-		size_t dot_pos = this->version.find('.', last_dot_pos);
-		// We run out of parts to compare the versions must be equal
-		if (dot_pos == std::string::npos) return 0;
-
-		size_t dot_pos2 = s.find('.', last_dot_pos2);
-		assert(dot_pos2 != std::string::npos);
+		dot_pos = this->version.find('.', last_dot_pos);
+		if (dot_pos == std::string::npos) {
+			was_last = true;
+			dot_pos = this->version.size();
+			dot_pos2 = s.size();
+		} else {
+			dot_pos2 = s.find('.', last_dot_pos2);
+			assert(dot_pos2 != std::string::npos);
+		}
 
 		long n1 = checked_strtol(this->version.substr(last_dot_pos, dot_pos - last_dot_pos));
 		long n2 = checked_strtol(s.substr(last_dot_pos2, dot_pos2 - last_dot_pos2));
@@ -54,6 +57,9 @@ auto LinuxVersion::compare(const std::string& s) -> int {
 
 		if (n1 < n2) return -1;
 
+		// We ran out of parts to compare the versions must be equal
+		if (was_last) return 0;
+
 		last_dot_pos = dot_pos + 1;
 		last_dot_pos2 = dot_pos2 + 1;
 	}
diff --git a/emper/lib/LinuxVersion.hpp b/emper/lib/LinuxVersion.hpp
index 6256cc40..ae437839 100644
--- a/emper/lib/LinuxVersion.hpp
+++ b/emper/lib/LinuxVersion.hpp
@@ -6,15 +6,18 @@
 
 #include <string>
 
+class TestLinuxVersion;
+
 namespace emper::lib {
 class LinuxVersion {
+	friend class ::TestLinuxVersion;
 	std::string version;
 
 	auto compare(const std::string& s) -> int;
 
- public:
-	static LinuxVersion linuxVersion;
+	LinuxVersion(std::string version) : version(version) {}
 
+ public:
 	LinuxVersion() {
 		struct utsname buf;
 		uname(&buf);
@@ -33,13 +36,8 @@ class LinuxVersion {
 };
 }	 // namespace emper::lib
 
-#define EMPER_LINUX_EQ(version) \
-	([]() -> bool { return empere::lib::LinuxVersion::linuxVersion == version; }())
-#define EMPER_LINUX_GT(version) \
-	([]() -> bool { return emper::lib::LinuxVersion::linuxVersion > version; }())
-#define EMPER_LINUX_GE(version) \
-	([]() -> bool { return emper::lib::LinuxVersion::linuxVersion >= version; }())
-#define EMPER_LINUX_LT(version) \
-	([]() -> bool { return emper::lib::LinuxVersion::linuxVersion < version; }())
-#define EMPER_LINUX_LE(version) \
-	([]() -> bool { return emper::lib::LinuxVersion::linuxVersion <= version; }())
+#define EMPER_LINUX_EQ(version) (emper::lib::LinuxVersion() == version)
+#define EMPER_LINUX_GT(version) (emper::lib::LinuxVersion() > version))
+#define EMPER_LINUX_GE(version) (emper::lib::LinuxVersion() >= version)
+#define EMPER_LINUX_LT(version) (emper::lib::LinuxVersion() < version)
+#define EMPER_LINUX_LE(version) (emper::lib::LinuxVersion() <= version)
diff --git a/tests/lib/LinuxVersionTest.cpp b/tests/lib/LinuxVersionTest.cpp
new file mode 100644
index 00000000..b14ec049
--- /dev/null
+++ b/tests/lib/LinuxVersionTest.cpp
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright © 2021 Florian Fischer
+#include <gtest/gtest.h>
+
+#include <string>
+#include <utility>
+
+#include "lib/LinuxVersion.hpp"
+
+using emper::lib::LinuxVersion;
+
+class TestLinuxVersion {
+ public:
+	static auto eq(std::string v1, const std::string& v2) -> bool {
+		return LinuxVersion(std::move(v1)) == v2;
+	}
+	static auto neq(std::string v1, const std::string& v2) -> bool {
+		return LinuxVersion(std::move(v1)) != v2;
+	}
+	static auto lt(std::string v1, const std::string& v2) -> bool {
+		return LinuxVersion(std::move(v1)) < v2;
+	}
+	static auto le(std::string v1, const std::string& v2) -> bool {
+		return LinuxVersion(std::move(v1)) <= v2;
+	}
+	static auto gt(std::string v1, const std::string& v2) -> bool {
+		return LinuxVersion(std::move(v1)) > v2;
+	}
+	static auto ge(std::string v1, const std::string& v2) -> bool {
+		return LinuxVersion(std::move(v1)) >= v2;
+	}
+};
+
+// NOLINTNEXTLINE(modernize-use-trailing-return-type)
+TEST(LinuxVersion, eq) {
+	ASSERT_TRUE(TestLinuxVersion::eq("5.13.0", "5.13.0"));
+	ASSERT_TRUE(TestLinuxVersion::eq("5.13-foo.0", "5.13.0"));
+}
+
+// NOLINTNEXTLINE(modernize-use-trailing-return-type)
+TEST(LinuxVersion, neq) {
+	ASSERT_TRUE(TestLinuxVersion::neq("4.1.0", "5.13.0"));
+	ASSERT_TRUE(TestLinuxVersion::neq("4.1", "5.13.0"));
+	ASSERT_TRUE(TestLinuxVersion::neq("5.14.0", "5.13.0"));
+	ASSERT_TRUE(TestLinuxVersion::neq("5.13.1", "5.13.2"));
+	ASSERT_TRUE(TestLinuxVersion::neq("5.13-foo.1", "5.13.2"));
+}
+
+// NOLINTNEXTLINE(modernize-use-trailing-return-type)
+TEST(LinuxVersion, lt) {
+	ASSERT_TRUE(TestLinuxVersion::lt("4.13", "5.14.0"));
+	ASSERT_TRUE(TestLinuxVersion::lt("5.13.0", "5.14.0"));
+	ASSERT_TRUE(TestLinuxVersion::lt("5.13-foo.1", "5.13.2"));
+	ASSERT_TRUE(TestLinuxVersion::lt("5.13.1-foo", "5.13.2"));
+}
+
+// NOLINTNEXTLINE(modernize-use-trailing-return-type)
+TEST(LinuxVersion, le) {
+	ASSERT_TRUE(TestLinuxVersion::le("4.13.0", "5.14.0"));
+	ASSERT_TRUE(TestLinuxVersion::le("4.14.0", "5.14.0"));
+}
+
+// NOLINTNEXTLINE(modernize-use-trailing-return-type)
+TEST(LinuxVersion, gt) {
+	ASSERT_TRUE(TestLinuxVersion::gt("5.14.0", "4.13"));
+	ASSERT_TRUE(TestLinuxVersion::gt("5.14.0", "5.13.0"));
+	ASSERT_TRUE(TestLinuxVersion::gt("5.13.2", "5.13-foo.1"));
+	ASSERT_TRUE(TestLinuxVersion::gt("5.13.2", "5.13.1-foo"));
+}
+
+// NOLINTNEXTLINE(modernize-use-trailing-return-type)
+TEST(LinuxVersion, ge) {
+	ASSERT_TRUE(TestLinuxVersion::ge("5.14.0", "4.13"));
+	ASSERT_TRUE(TestLinuxVersion::ge("5.14.0", "5.14.0"));
+}
diff --git a/tests/lib/meson.build b/tests/lib/meson.build
index 76e0ecd7..1bba5e40 100644
--- a/tests/lib/meson.build
+++ b/tests/lib/meson.build
@@ -13,4 +13,12 @@ tests += [
 		'gtest': true,
 		'is_parallel': true,
 	},
+
+	{
+		'source': files('LinuxVersionTest.cpp'),
+		'name': 'LinuxVersionTest',
+		'description': 'Tests LinuxVerison',
+		'gtest': true,
+		'is_parallel': true,
+	},
 ]
-- 
GitLab