浏览代码

Adding test coverage

Sam Jaffe 7 年之前
父节点
当前提交
69c68c52d4
共有 6 个文件被更改,包括 451 次插入19 次删除
  1. 214 0
      dice-roll.xcodeproj/project.pbxproj
  2. 67 0
      dice-roll.xcodeproj/xcshareddata/xcschemes/dice-td.xcscheme
  3. 22 0
      dice-td/Info.plist
  4. 1 19
      src/die.cxx
  5. 30 0
      src/exception.cxx
  6. 117 0
      test/dice_test.cxx

+ 214 - 0
dice-roll.xcodeproj/project.pbxproj

@@ -11,6 +11,10 @@
 		CD38F50E21C83929007A732C /* roll.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD8F1ABD21B31E9E00CBB3CA /* roll.cxx */; };
 		CD38F50F21C83936007A732C /* libdice.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CD38F50921C83912007A732C /* libdice.dylib */; };
 		CD38F51221C8397A007A732C /* libshared_random_generator.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CDED6A6021B2F89900AB91D0 /* libshared_random_generator.dylib */; };
+		CD38F52B21C87771007A732C /* libdice.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CD38F50921C83912007A732C /* libdice.dylib */; };
+		CD38F53321C87787007A732C /* GoogleMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD38F51B21C87757007A732C /* GoogleMock.framework */; };
+		CD38F53521C87799007A732C /* dice_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F53421C87799007A732C /* dice_test.cxx */; };
+		CD38F53721C89493007A732C /* exception.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F53621C89493007A732C /* exception.cxx */; };
 		CDED6A2721B2F28A00AB91D0 /* main.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDED6A2621B2F28A00AB91D0 /* main.cxx */; };
 /* End PBXBuildFile section */
 
@@ -22,6 +26,48 @@
 			remoteGlobalIDString = CD38F50821C83912007A732C;
 			remoteInfo = dice;
 		};
+		CD38F51A21C87757007A732C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD38F51321C87757007A732C /* GoogleMock.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = 05818F861A685AEA0072A469;
+			remoteInfo = GoogleMock;
+		};
+		CD38F51C21C87757007A732C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD38F51321C87757007A732C /* GoogleMock.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = 05E96ABD1A68600C00204102;
+			remoteInfo = gmock;
+		};
+		CD38F51E21C87757007A732C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD38F51321C87757007A732C /* GoogleMock.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = 05E96B1F1A68634900204102;
+			remoteInfo = gtest;
+		};
+		CD38F52021C87757007A732C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD38F51321C87757007A732C /* GoogleMock.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = 05818F901A685AEA0072A469;
+			remoteInfo = GoogleMockTests;
+		};
+		CD38F52C21C87771007A732C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CDED6A1B21B2F28A00AB91D0 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = CD38F50821C83912007A732C;
+			remoteInfo = dice;
+		};
+		CD38F53121C87783007A732C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD38F51321C87757007A732C /* GoogleMock.xcodeproj */;
+			proxyType = 1;
+			remoteGlobalIDString = 05818F851A685AEA0072A469;
+			remoteInfo = GoogleMock;
+		};
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -38,6 +84,11 @@
 
 /* Begin PBXFileReference section */
 		CD38F50921C83912007A732C /* libdice.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libdice.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD38F51321C87757007A732C /* GoogleMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GoogleMock.xcodeproj; path = "../../gmock-xcode-master/GoogleMock.xcodeproj"; sourceTree = "<group>"; };
+		CD38F52621C87771007A732C /* dice-td.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "dice-td.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD38F52A21C87771007A732C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		CD38F53421C87799007A732C /* dice_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dice_test.cxx; sourceTree = "<group>"; };
+		CD38F53621C89493007A732C /* exception.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = exception.cxx; sourceTree = "<group>"; };
 		CD8F1ABC21B31E9E00CBB3CA /* roll.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = roll.h; sourceTree = "<group>"; };
 		CD8F1ABD21B31E9E00CBB3CA /* roll.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = roll.cxx; sourceTree = "<group>"; };
 		CDED6A2321B2F28A00AB91D0 /* simple_dice */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = simple_dice; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -58,6 +109,15 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		CD38F52321C87771007A732C /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				CD38F53321C87787007A732C /* GoogleMock.framework in Frameworks */,
+				CD38F52B21C87771007A732C /* libdice.dylib in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		CDED6A2021B2F28A00AB91D0 /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
@@ -69,12 +129,33 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		CD38F51421C87757007A732C /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				CD38F51B21C87757007A732C /* GoogleMock.framework */,
+				CD38F51D21C87757007A732C /* gmock.framework */,
+				CD38F51F21C87757007A732C /* gtest.framework */,
+				CD38F52121C87757007A732C /* GoogleMockTests.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		CD38F52721C87771007A732C /* dice-td */ = {
+			isa = PBXGroup;
+			children = (
+				CD38F52A21C87771007A732C /* Info.plist */,
+			);
+			path = "dice-td";
+			sourceTree = "<group>";
+		};
 		CDED6A1A21B2F28A00AB91D0 = {
 			isa = PBXGroup;
 			children = (
+				CD38F51321C87757007A732C /* GoogleMock.xcodeproj */,
 				CDED6A2E21B2F2C300AB91D0 /* include */,
 				CDED6A2D21B2F2B200AB91D0 /* test */,
 				CDED6A2521B2F28A00AB91D0 /* src */,
+				CD38F52721C87771007A732C /* dice-td */,
 				CDED6A2421B2F28A00AB91D0 /* Products */,
 				CDED6A5F21B2F89900AB91D0 /* Frameworks */,
 			);
@@ -85,6 +166,7 @@
 			children = (
 				CDED6A2321B2F28A00AB91D0 /* simple_dice */,
 				CD38F50921C83912007A732C /* libdice.dylib */,
+				CD38F52621C87771007A732C /* dice-td.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -94,6 +176,7 @@
 			children = (
 				CDED6A2621B2F28A00AB91D0 /* main.cxx */,
 				CDED6A3021B2F2DC00AB91D0 /* die.cxx */,
+				CD38F53621C89493007A732C /* exception.cxx */,
 				CD8F1ABD21B31E9E00CBB3CA /* roll.cxx */,
 			);
 			path = src;
@@ -102,6 +185,7 @@
 		CDED6A2D21B2F2B200AB91D0 /* test */ = {
 			isa = PBXGroup;
 			children = (
+				CD38F53421C87799007A732C /* dice_test.cxx */,
 			);
 			path = test;
 			sourceTree = "<group>";
@@ -155,6 +239,25 @@
 			productReference = CD38F50921C83912007A732C /* libdice.dylib */;
 			productType = "com.apple.product-type.library.dynamic";
 		};
+		CD38F52521C87771007A732C /* dice-td */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = CD38F52E21C87771007A732C /* Build configuration list for PBXNativeTarget "dice-td" */;
+			buildPhases = (
+				CD38F52221C87771007A732C /* Sources */,
+				CD38F52321C87771007A732C /* Frameworks */,
+				CD38F52421C87771007A732C /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				CD38F53221C87783007A732C /* PBXTargetDependency */,
+				CD38F52D21C87771007A732C /* PBXTargetDependency */,
+			);
+			name = "dice-td";
+			productName = "dice-td";
+			productReference = CD38F52621C87771007A732C /* dice-td.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
 		CDED6A2221B2F28A00AB91D0 /* simple_dice */ = {
 			isa = PBXNativeTarget;
 			buildConfigurationList = CDED6A2A21B2F28A00AB91D0 /* Build configuration list for PBXNativeTarget "simple_dice" */;
@@ -185,6 +288,9 @@
 					CD38F50821C83912007A732C = {
 						CreatedOnToolsVersion = 10.1;
 					};
+					CD38F52521C87771007A732C = {
+						CreatedOnToolsVersion = 10.1;
+					};
 					CDED6A2221B2F28A00AB91D0 = {
 						CreatedOnToolsVersion = 10.1;
 					};
@@ -200,14 +306,62 @@
 			mainGroup = CDED6A1A21B2F28A00AB91D0;
 			productRefGroup = CDED6A2421B2F28A00AB91D0 /* Products */;
 			projectDirPath = "";
+			projectReferences = (
+				{
+					ProductGroup = CD38F51421C87757007A732C /* Products */;
+					ProjectRef = CD38F51321C87757007A732C /* GoogleMock.xcodeproj */;
+				},
+			);
 			projectRoot = "";
 			targets = (
 				CDED6A2221B2F28A00AB91D0 /* simple_dice */,
 				CD38F50821C83912007A732C /* dice */,
+				CD38F52521C87771007A732C /* dice-td */,
 			);
 		};
 /* End PBXProject section */
 
+/* Begin PBXReferenceProxy section */
+		CD38F51B21C87757007A732C /* GoogleMock.framework */ = {
+			isa = PBXReferenceProxy;
+			fileType = wrapper.framework;
+			path = GoogleMock.framework;
+			remoteRef = CD38F51A21C87757007A732C /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+		CD38F51D21C87757007A732C /* gmock.framework */ = {
+			isa = PBXReferenceProxy;
+			fileType = wrapper.framework;
+			path = gmock.framework;
+			remoteRef = CD38F51C21C87757007A732C /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+		CD38F51F21C87757007A732C /* gtest.framework */ = {
+			isa = PBXReferenceProxy;
+			fileType = wrapper.framework;
+			path = gtest.framework;
+			remoteRef = CD38F51E21C87757007A732C /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+		CD38F52121C87757007A732C /* GoogleMockTests.xctest */ = {
+			isa = PBXReferenceProxy;
+			fileType = wrapper.cfbundle;
+			path = GoogleMockTests.xctest;
+			remoteRef = CD38F52021C87757007A732C /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+/* End PBXReferenceProxy section */
+
+/* Begin PBXResourcesBuildPhase section */
+		CD38F52421C87771007A732C /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
 /* Begin PBXSourcesBuildPhase section */
 		CD38F50621C83912007A732C /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
@@ -215,6 +369,15 @@
 			files = (
 				CD38F50E21C83929007A732C /* roll.cxx in Sources */,
 				CD38F50D21C83926007A732C /* die.cxx in Sources */,
+				CD38F53721C89493007A732C /* exception.cxx in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		CD38F52221C87771007A732C /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				CD38F53521C87799007A732C /* dice_test.cxx in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -234,6 +397,16 @@
 			target = CD38F50821C83912007A732C /* dice */;
 			targetProxy = CD38F51021C83950007A732C /* PBXContainerItemProxy */;
 		};
+		CD38F52D21C87771007A732C /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = CD38F50821C83912007A732C /* dice */;
+			targetProxy = CD38F52C21C87771007A732C /* PBXContainerItemProxy */;
+		};
+		CD38F53221C87783007A732C /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			name = GoogleMock;
+			targetProxy = CD38F53121C87783007A732C /* PBXContainerItemProxy */;
+		};
 /* End PBXTargetDependency section */
 
 /* Begin XCBuildConfiguration section */
@@ -261,6 +434,38 @@
 			};
 			name = Release;
 		};
+		CD38F52F21C87771007A732C /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = "dice-td/Info.plist";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+					"@loader_path/../Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = "leumasjaffe.dice-td";
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		CD38F53021C87771007A732C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = "dice-td/Info.plist";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+					"@loader_path/../Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = "leumasjaffe.dice-td";
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
 		CDED6A2821B2F28A00AB91D0 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
@@ -410,6 +615,15 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		CD38F52E21C87771007A732C /* Build configuration list for PBXNativeTarget "dice-td" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				CD38F52F21C87771007A732C /* Debug */,
+				CD38F53021C87771007A732C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 		CDED6A1E21B2F28A00AB91D0 /* Build configuration list for PBXProject "dice-roll" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (

+ 67 - 0
dice-roll.xcodeproj/xcshareddata/xcschemes/dice-td.xcscheme

@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1010"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      codeCoverageEnabled = "YES"
+      onlyGenerateCoverageForSpecifiedTargets = "YES"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <CodeCoverageTargets>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "CD38F50821C83912007A732C"
+            BuildableName = "libdice.dylib"
+            BlueprintName = "dice"
+            ReferencedContainer = "container:dice-roll.xcodeproj">
+         </BuildableReference>
+      </CodeCoverageTargets>
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "CD38F52521C87771007A732C"
+               BuildableName = "dice-td.xctest"
+               BlueprintName = "dice-td"
+               ReferencedContainer = "container:dice-roll.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 22 - 0
dice-td/Info.plist

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>BNDL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+</dict>
+</plist>

+ 1 - 19
src/die.cxx

@@ -9,7 +9,6 @@
 #include "die.h"
 #include "exception.h"
 
-#include <cmath>
 #include <iostream>
 #include <sstream>
 
@@ -20,15 +19,6 @@ static void advance_over_whitespace(std::istream & in, char const * also = "") {
   }
 }
 
-static std::string carrot(long long pos) {
-  if (pos == -1) return "<END>"; // For safety
-  std::string out;
-  // resize(0) followed by back() will cause UB
-  out.resize(std::max(pos, 1ll), '~');
-  out.back() = '^';
-  return out;
-}
-
 namespace dice {
   int sgn(sign s) { return s == MINUS ? -1 : 1; }
   std::string str(sign s) {
@@ -43,15 +33,6 @@ namespace dice {
   }
 
   mod::operator int() const { return sgn(sign) * value; }
-}
-
-namespace dice {
-  unexpected_token::unexpected_token(std::string const & reason, long long pos)
-      : std::runtime_error(reason), position(pos) {}
-
-  std::string unexpected_token::pointer(long long backup_length) const {
-    return carrot(position == -1 ? backup_length : position);
-  }
 
   std::ostream & operator<<(std::ostream & out, dice const & d) {
     if (d.num != 1) out << d.num << '{';
@@ -161,6 +142,7 @@ namespace dice {
     int value{1};
     advance_over_whitespace(in);
     if (isnumber(in.peek())) { in >> value; }
+    advance_over_whitespace(in);
     switch (in.peek()) {
     case 'd':
     case 'D':

+ 30 - 0
src/exception.cxx

@@ -0,0 +1,30 @@
+//
+//  exception.cxx
+//  dice
+//
+//  Created by Sam Jaffe on 12/17/18.
+//  Copyright © 2018 Sam Jaffe. All rights reserved.
+//
+
+#include "exception.h"
+
+#include <cmath>
+#include <string>
+
+static std::string carrot(long long pos) {
+  if (pos == -1) return "<END>"; // For safety
+  std::string out;
+  // resize(0) followed by back() will cause UB
+  out.resize(std::max(pos, 1ll), '~');
+  out.back() = '^';
+  return out;
+}
+
+namespace dice {
+  unexpected_token::unexpected_token(std::string const & reason, long long pos)
+  : std::runtime_error(reason), position(pos) {}
+  
+  std::string unexpected_token::pointer(long long backup_length) const {
+    return carrot(position == -1 ? backup_length : position);
+  }
+}

+ 117 - 0
test/dice_test.cxx

@@ -0,0 +1,117 @@
+//
+//  dice_test.cxx
+//  dice-td
+//
+//  Created by Sam Jaffe on 12/17/18.
+//  Copyright © 2018 Sam Jaffe. All rights reserved.
+//
+
+#include <gmock/gmock.h>
+
+#include "die.h"
+#include "exception.h"
+
+#include <sstream>
+
+using unsigned_die = std::pair<int, int>;
+namespace dice {
+  bool operator==(die const & lhs, die const & rhs) {
+    return lhs.sgn == rhs.sgn && lhs.num == rhs.num && lhs.sides == rhs.sides;
+  }
+  
+  bool operator==(die const & lhs, unsigned_die const & rhs) {
+    return lhs.num == rhs.first && lhs.sides == rhs.second;
+  }
+}
+
+using namespace ::testing;
+
+TEST(DiceTest, ThrowsOnEmptyString) {
+  EXPECT_THROW(dice::from_string(""), dice::unexpected_token);
+}
+
+TEST(DiceTest, ThrowsOnOpeningArithmetic) {
+  EXPECT_THROW(dice::from_string("+5"), dice::unexpected_token);
+  EXPECT_THROW(dice::from_string("-5"), dice::unexpected_token);
+  EXPECT_THROW(dice::from_string("+1d4"), dice::unexpected_token);
+  EXPECT_THROW(dice::from_string("-1d4"), dice::unexpected_token);
+}
+
+TEST(DiceTest, Implicit1dN) {
+  dice::dice capture;
+  EXPECT_NO_THROW(capture = dice::from_string("d4"));
+  EXPECT_THAT(capture.num, Eq(1));
+  EXPECT_THAT(capture.of, SizeIs(1));
+  EXPECT_THAT(capture.of[0], Eq(unsigned_die(1, 4)));
+}
+
+TEST(DiceTest, Explicit1dN) {
+  dice::dice capture;
+  EXPECT_NO_THROW(capture = dice::from_string("1d4"));
+  EXPECT_THAT(capture.num, Eq(1));
+  EXPECT_THAT(capture.of, SizeIs(1));
+  EXPECT_THAT(capture.of[0], Eq(unsigned_die(1, 4)));
+}
+
+TEST(DiceTest, CannotImplicitNumberOfSides) {
+  EXPECT_THROW(dice::from_string("1d"), dice::unexpected_token);
+}
+
+TEST(DiceTest, AllowsMultipleDice) {
+  dice::dice capture;
+  EXPECT_NO_THROW(capture = dice::from_string("1d4+1d6"));
+  EXPECT_THAT(capture.of, SizeIs(2));
+  EXPECT_THAT(capture.of, ElementsAre(Eq(unsigned_die(1, 4)),
+                                      Eq(unsigned_die(1, 6))));
+}
+
+TEST(DiceTest, CanIncludeConstant) {
+  dice::dice capture;
+  EXPECT_NO_THROW(capture = dice::from_string("1d4+1"));
+  EXPECT_THAT(capture.of, SizeIs(1));
+  EXPECT_THAT(capture.modifier, SizeIs(1));
+  EXPECT_THAT(capture.modifier[0].value, 1);
+}
+
+TEST(DiceTest, ConstantSignIsInSgnMember) {
+  dice::dice capture;
+  EXPECT_NO_THROW(capture = dice::from_string("1d4-1"));
+  EXPECT_THAT(capture.of, SizeIs(1));
+  EXPECT_THAT(capture.modifier, SizeIs(1));
+  EXPECT_THAT(capture.modifier[0].value, 1);
+}
+
+TEST(DiceTest, ThrowsIfUnterminatedArithmatic) {
+  EXPECT_THROW(dice::from_string("1d4+"), dice::unexpected_token);
+}
+
+TEST(DiceTest, CanProduceMultiRollExpression) {
+  dice::dice capture;
+  EXPECT_NO_THROW(capture = dice::from_string("2{d4}"));
+  EXPECT_THAT(capture.num, Eq(2));
+  EXPECT_THAT(capture.of, SizeIs(1));
+}
+
+TEST(DiceTest, MultiRollWillThrowIfNoEndBrace) {
+  EXPECT_THROW(dice::from_string("2{d4"), dice::unexpected_token);
+}
+
+TEST(DiceTest, IgnoresWhitespace) {
+  dice::dice capture;
+  EXPECT_NO_THROW(capture = dice::from_string("2 { d 4 + 5 }"));
+  EXPECT_THAT(capture.num, Eq(2));
+  EXPECT_THAT(capture.of, SizeIs(1));
+  EXPECT_THAT(capture.modifier, SizeIs(1));
+}
+
+TEST(DiceSerialTest, StringFormIsExplicit) {
+  std::stringstream ss;
+  ss << dice::from_string("2{2d6-d4+5}");
+  EXPECT_THAT(ss.str(), Eq("2{2d6-1d4+5}"));
+}
+
+TEST(DiceSerialTest, StringFormDoesNotPreserveWhitespace) {
+  std::stringstream ss;
+  ss << dice::from_string("2 { d 4 + 5 }");
+  EXPECT_THAT(ss.str(), Eq("2{1d4+5}"));
+}