Browse Source

test: add test coverage

Sam Jaffe 2 years ago
parent
commit
cc6b870b0f

+ 3 - 0
.gitmodules

@@ -4,3 +4,6 @@
 [submodule "external/magic_enum"]
 	path = external/magic_enum
 	url = https://github.com/Neargye/magic_enum.git
+[submodule "external/expect"]
+	path = external/expect
+	url = ssh://git@gogs.sjaffe.name:3000/sjjaffe/cpp-expect

+ 1 - 0
external/expect

@@ -0,0 +1 @@
+Subproject commit d044ccd4c55d936f71e5c97f0f05f15bfaac859e

+ 1 - 1
external/string-utils

@@ -1 +1 @@
-Subproject commit a66ac70c36b4c52a7f6ddf5bc0118892ad8175ca
+Subproject commit bf98bd04e6dfa3a60c354d58ba453265b0d2e121

+ 11 - 4
include/serializer/jsonizer.tpp

@@ -15,7 +15,9 @@
 #include <magic_enum.hpp>
 
 #include <serializer/jsonizer.h>
+#include <serializer/strconv.h>
 #include <serializer/traits.h>
+#include <string_utils/cast.h>
 
 namespace serializer {
 template <typename T, size_t... Is>
@@ -30,13 +32,16 @@ template <typename T> Json::Value Jsonizer::to_json(T const & value) const {
   if constexpr (detail::has_serial_type_v<T>) {
     return to_json(static_cast<typename T::serial_type>(value));
   } else if constexpr (std::is_enum_v<T>) {
-    return magic_enum::enum_name(value);
+    constexpr auto type = magic_enum::as_flags<magic_enum::detail::is_flags_v<T>>;
+    return std::string(magic_enum::enum_name<type>(value));
   } else if constexpr (detail::is_tuple_v<T>) {
     return to_json(value, std::make_index_sequence<std::tuple_size_v<T>>());
-  } else if constexpr (std::is_same_v<T, std::string>) {
-    return value;
+  } else if constexpr (std::is_constructible_v<std::string, T>) {
+    return std::string(value);
   } else if constexpr (std::is_floating_point_v<T>) {
     return static_cast<double>(value);
+  } else if constexpr (std::is_same_v<bool, T>) {
+    return value;
   } else if constexpr (std::is_arithmetic_v<T>) {
     return static_cast<int>(value);
   } else if constexpr (detail::is_associative_container_v<T>) {
@@ -78,7 +83,7 @@ void Jsonizer::from_json(T & value, Json::Value const & json) const {
   if constexpr (detail::has_serial_type_v<T>) {
     value = T(from_json<typename T::serial_type>(json));
   } else if constexpr (std::is_enum_v<T>) {
-    if (!from_string(value, json.asString())) {
+    if (!string_utils::cast(value, json.asString())) {
       throw std::invalid_argument("Cannot cast to enum: " + json.asString());
     }
   } else if constexpr (std::is_same_v<std::string, T>) {
@@ -116,6 +121,8 @@ void Jsonizer::from_json(T & value, Json::Value const & json) const {
     }
   } else if constexpr (std::is_floating_point_v<T>) {
     value = static_cast<T>(json.asDouble());
+  } else if constexpr (std::is_same_v<bool, T>) {
+    value = json.asBool();
   } else if constexpr (std::is_arithmetic_v<T>) {
     value = static_cast<T>(json.asInt());
   } else {

+ 2 - 1
include/serializer/strconv.h

@@ -24,7 +24,8 @@ template <typename T> std::string to_string(T const * t) { return to_string(*t);
 
 template <typename T> std::string to_string(T const &elem) {
   if constexpr (std::is_enum_v<T>) {
-    return std::string(magic_enum::enum_name(value));
+    constexpr auto type = magic_enum::as_flags<magic_enum::detail::is_flags_v<T>>;
+    return std::string(magic_enum::enum_name<type>(elem));
   } else {
     static_assert(detail::always_false<T>{});
   }

+ 16 - 0
serializer.xcodeproj/project.pbxproj

@@ -10,6 +10,8 @@
 		CD592C0F29C3D932009AC14E /* serializer in Headers */ = {isa = PBXBuildFile; fileRef = CD592C0629C2A5EB009AC14E /* serializer */; settings = {ATTRIBUTES = (Public, ); }; };
 		CD592C2229C3DB79009AC14E /* jsonizer_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD592C2129C3DB79009AC14E /* jsonizer_test.cxx */; };
 		CD592C2729C3DC65009AC14E /* libjsoncpp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD592C2629C3DC65009AC14E /* libjsoncpp.a */; };
+		CD592C4629C3E153009AC14E /* jsonizier.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD592C4329C3E001009AC14E /* jsonizier.cxx */; };
+		CD592C4729C3E1A2009AC14E /* GoogleMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD592C3029C3DC76009AC14E /* GoogleMock.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -79,6 +81,7 @@
 		CD592C2629C3DC65009AC14E /* libjsoncpp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libjsoncpp.a; path = ../../../../../../../../opt/local/lib/libjsoncpp.a; sourceTree = "<group>"; };
 		CD592C2829C3DC76009AC14E /* GoogleMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GoogleMock.xcodeproj; path = "../../../gmock-xcode-master/GoogleMock.xcodeproj"; sourceTree = "<group>"; };
 		CD592C3729C3DC84009AC14E /* string-utils.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "string-utils.xcodeproj"; path = "external/string-utils/string-utils.xcodeproj"; sourceTree = "<group>"; };
+		CD592C4329C3E001009AC14E /* jsonizier.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = jsonizier.cxx; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -94,6 +97,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				CD592C2729C3DC65009AC14E /* libjsoncpp.a in Frameworks */,
+				CD592C4729C3E1A2009AC14E /* GoogleMock.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -107,6 +111,7 @@
 				CD592C3729C3DC84009AC14E /* string-utils.xcodeproj */,
 				CD592C0629C2A5EB009AC14E /* serializer */,
 				CD592C0429C2A5E0009AC14E /* include */,
+				CD592C4229C3DFEE009AC14E /* src */,
 				CD592C1229C3DB22009AC14E /* test */,
 				CD592C1829C3DB32009AC14E /* serializer-test */,
 				CD592BFE29C2A5BF009AC14E /* Products */,
@@ -188,6 +193,14 @@
 			name = Products;
 			sourceTree = "<group>";
 		};
+		CD592C4229C3DFEE009AC14E /* src */ = {
+			isa = PBXGroup;
+			children = (
+				CD592C4329C3E001009AC14E /* jsonizier.cxx */,
+			);
+			path = src;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXHeadersBuildPhase section */
@@ -352,6 +365,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				CD592C2229C3DB79009AC14E /* jsonizer_test.cxx in Sources */,
+				CD592C4629C3E153009AC14E /* jsonizier.cxx in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -426,6 +440,7 @@
 					"$(PROJECT_DIR)/include",
 					"$(PROJECT_DIR)/external/string-utils/include",
 					"$(PROJECT_DIR)/external/magic_enum/include",
+					"$(PROJECT_DIR)/external/expect/include",
 				);
 			};
 			name = Debug;
@@ -483,6 +498,7 @@
 					"$(PROJECT_DIR)/include",
 					"$(PROJECT_DIR)/external/string-utils/include",
 					"$(PROJECT_DIR)/external/magic_enum/include",
+					"$(PROJECT_DIR)/external/expect/include",
 				);
 			};
 			name = Release;

+ 25 - 0
src/jsonizier.cxx

@@ -0,0 +1,25 @@
+//
+//  jsonizier.cxx
+//  serializer
+//
+//  Created by Sam Jaffe on 3/16/23.
+//
+
+#include <serializer/jsonizer.h>
+
+#include <expect/expect.hpp>
+
+#include <serializer/shared_cache.h>
+
+
+namespace serializer {
+struct SharedCache::Impl {};
+
+SharedCache::SharedCache() : p_impl(new Impl) {}
+SharedCache::~SharedCache() {}
+
+Jsonizer::Jsonizer() : p_cache(std::make_shared<SharedCache>()) {}
+Jsonizer::Jsonizer(std::shared_ptr<SharedCache> cache) : p_cache(cache) {
+  expects(p_cache != nullptr);
+}
+}

+ 72 - 0
test/jsonizer_test.cxx

@@ -7,5 +7,77 @@
 
 #include "xcode_gtest_helper.h"
 
+#include <map>
+#include <set>
+#include <vector>
+
 #include <serializer/jsonizer.tpp>
 
+#define CONCAT2(A, B) A##B
+#define CONCAT(A, B) CONCAT2(A, B)
+
+#define EXPECT_ROUNDTRIP(izer, value, as_json) \
+  Json::Value CONCAT(json_, __LINE__) = izer.to_json(value); \
+  EXPECT_THAT(CONCAT(json_, __LINE__), as_json); \
+  EXPECT_THAT(izer.from_json<decltype(value)>(CONCAT(json_, __LINE__)), value)
+
+using std::operator""s;
+using std::operator""sv;
+using namespace magic_enum::bitwise_operators;
+
+enum class Color { RED, BLUE, GREEN };
+enum class Mask { LEFT = 1, RIGHT = 2 };
+
+Json::Value operator""_json(char const *str, size_t len) {
+  Json::Value rval;
+  Json::Reader reader;
+  if (!reader.parse(str, str + len, rval)) {
+    throw std::invalid_argument(reader.getFormattedErrorMessages());
+  }
+  return rval;
+}
+
+TEST(JsonizerTest, ParsesNumbers) {
+  serializer::Jsonizer izer;
+  EXPECT_ROUNDTRIP(izer, 1.5f, "1.5"_json);
+  EXPECT_ROUNDTRIP(izer, 1.5, "1.5"_json);
+  EXPECT_ROUNDTRIP(izer, 3, "3"_json);
+  EXPECT_ROUNDTRIP(izer, true, "true"_json);
+}
+
+TEST(JsonizerTest, ParsesStrings) {
+  serializer::Jsonizer izer;
+  EXPECT_ROUNDTRIP(izer, "hello"s, "\"hello\""_json);
+  EXPECT_THAT(izer.to_json("hello"), "\"hello\""_json);
+  EXPECT_THAT(izer.to_json("hello"sv), "\"hello\""_json);
+}
+
+TEST(JsonizerTest, ParsesEnums) {
+  serializer::Jsonizer izer;
+  EXPECT_ROUNDTRIP(izer, Color::RED, "\"RED\""_json);
+  EXPECT_TRUE(magic_enum::detail::is_flags_v<Mask>);
+  EXPECT_ROUNDTRIP(izer, Mask::LEFT | Mask::RIGHT, "\"LEFT|RIGHT\""_json);
+}
+
+TEST(JsonizerTest, ParsesTuples) {
+  serializer::Jsonizer izer;
+  EXPECT_ROUNDTRIP(izer, std::make_pair(1, true), "[1, true]"_json);
+  EXPECT_ROUNDTRIP(izer, std::make_tuple(1, true, 1.5, "hi"s),
+                   "[1, true, 1.5, \"hi\"]"_json);
+}
+
+TEST(JsonizerTest, ParsesCollection) {
+  serializer::Jsonizer izer;
+  EXPECT_ROUNDTRIP(izer, std::vector({1, 2, 3, 1}), "[1, 2, 3, 1]"_json);
+  EXPECT_ROUNDTRIP(izer, std::set({1, 2, 3, 1}), "[1, 2, 3]"_json);
+}
+
+TEST(JsonizerTest, ParsesAssociative) {
+  serializer::Jsonizer izer;
+  EXPECT_ROUNDTRIP(izer, (std::map<int, int>{{1, 2}, {3, 1}}),
+                   "{\"1\":2, \"3\":1}"_json);
+  
+  std::map<int, int> ex;
+  izer.from_json(ex, "[[1, 2], [3, 1]]"_json);
+  EXPECT_THAT(ex, (std::map<int, int>{{1, 2}, {3, 1}}));
+}