Browse Source

unittest: unix make check && osx xctest done

Pal Lockheart 7 years ago
parent
commit
e9991b6aa1
9 changed files with 552 additions and 43 deletions
  1. 3 0
      .gitmodules
  2. 1 0
      3rd/googletest
  3. 191 37
      macos/Pal.xcodeproj/project.pbxproj
  4. 264 0
      macos/PalTests/GoogleTests.mm
  5. 22 0
      macos/PalTests/Info.plist
  6. 3 0
      main.c
  7. 26 0
      tests/test_swprintf.cpp
  8. 6 0
      tests/testmain.cpp
  9. 36 6
      unix/Makefile

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "3rd/googletest"]
+	path = 3rd/googletest
+	url = https://github.com/google/googletest

+ 1 - 0
3rd/googletest

@@ -0,0 +1 @@
+Subproject commit 51143d5b62521f71020ada4ba1b6b44f3a6749bb

+ 191 - 37
macos/Pal.xcodeproj/project.pbxproj

@@ -66,44 +66,27 @@
 		7104FDB40D772FBC00A97E53 /* player.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7104FDA50D772FBC00A97E53 /* player.cpp */; };
 		7104FDB60D772FBC00A97E53 /* rix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7104FDA70D772FBC00A97E53 /* rix.cpp */; };
 		71147E4014085E31003FB2DB /* surroundopl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 71147E3E14085E31003FB2DB /* surroundopl.cpp */; };
-		71147E4114085E31003FB2DB /* surroundopl.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71147E3F14085E31003FB2DB /* surroundopl.h */; };
-		7138FD0E1424E4810060DE76 /* demuopl.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 7138FD0B1424E4810060DE76 /* demuopl.h */; };
 		7138FD0F1424E4810060DE76 /* dosbox_opl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7138FD0C1424E4810060DE76 /* dosbox_opl.cpp */; };
-		7138FD101424E4810060DE76 /* dosbox_opl.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 7138FD0D1424E4810060DE76 /* dosbox_opl.h */; };
 		716EB9BC0D77318900D5DE1F /* game.c in Sources */ = {isa = PBXBuildFile; fileRef = 7104FD3F0D772F6300A97E53 /* game.c */; };
 		716EB9C70D77340300D5DE1F /* sdlpal.icns in Resources */ = {isa = PBXBuildFile; fileRef = 716EB9C60D77340300D5DE1F /* sdlpal.icns */; };
 		716EB9CA0D77347B00D5DE1F /* ui.c in Sources */ = {isa = PBXBuildFile; fileRef = 7104FD5E0D772F6300A97E53 /* ui.c */; };
 		717AE630182663E100B10A11 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 717AE62F182663E100B10A11 /* SDL2.framework */; };
 		71E23E9113F6D1AD001287B6 /* bit.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E23E7113F6D1AD001287B6 /* bit.c */; };
-		71E23E9213F6D1AD001287B6 /* bit.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E7213F6D1AD001287B6 /* bit.h */; };
 		71E23E9313F6D1AD001287B6 /* D.dat in Resources */ = {isa = PBXBuildFile; fileRef = 71E23E7313F6D1AD001287B6 /* D.dat */; };
 		71E23E9413F6D1AD001287B6 /* decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E23E7413F6D1AD001287B6 /* decoder.c */; };
-		71E23E9513F6D1AD001287B6 /* decoder.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E7513F6D1AD001287B6 /* decoder.h */; };
 		71E23E9613F6D1AD001287B6 /* fixed.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E23E7613F6D1AD001287B6 /* fixed.c */; };
-		71E23E9713F6D1AD001287B6 /* fixed.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E7713F6D1AD001287B6 /* fixed.h */; };
 		71E23E9813F6D1AD001287B6 /* frame.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E23E7813F6D1AD001287B6 /* frame.c */; };
-		71E23E9913F6D1AD001287B6 /* frame.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E7913F6D1AD001287B6 /* frame.h */; };
 		71E23E9A13F6D1AD001287B6 /* huffman.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E23E7A13F6D1AD001287B6 /* huffman.c */; };
-		71E23E9B13F6D1AD001287B6 /* huffman.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E7B13F6D1AD001287B6 /* huffman.h */; };
 		71E23E9C13F6D1AD001287B6 /* imdct_s.dat in Resources */ = {isa = PBXBuildFile; fileRef = 71E23E7C13F6D1AD001287B6 /* imdct_s.dat */; };
 		71E23E9D13F6D1AD001287B6 /* layer3.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E23E7D13F6D1AD001287B6 /* layer3.c */; };
-		71E23E9E13F6D1AD001287B6 /* layer3.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E7E13F6D1AD001287B6 /* layer3.h */; };
 		71E23E9F13F6D1AD001287B6 /* layer12.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E23E7F13F6D1AD001287B6 /* layer12.c */; };
-		71E23EA013F6D1AD001287B6 /* layer12.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E8013F6D1AD001287B6 /* layer12.h */; };
-		71E23EA113F6D1AD001287B6 /* libmad_config.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E8113F6D1AD001287B6 /* libmad_config.h */; };
-		71E23EA213F6D1AD001287B6 /* libmad_global.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E8213F6D1AD001287B6 /* libmad_global.h */; };
-		71E23EA313F6D1AD001287B6 /* mad.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E8313F6D1AD001287B6 /* mad.h */; };
 		71E23EA413F6D1AD001287B6 /* music_mad.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E23E8413F6D1AD001287B6 /* music_mad.c */; };
-		71E23EA513F6D1AD001287B6 /* music_mad.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E8513F6D1AD001287B6 /* music_mad.h */; };
 		71E23EA613F6D1AD001287B6 /* qc_table.dat in Resources */ = {isa = PBXBuildFile; fileRef = 71E23E8613F6D1AD001287B6 /* qc_table.dat */; };
 		71E23EA713F6D1AD001287B6 /* rq_table.dat in Resources */ = {isa = PBXBuildFile; fileRef = 71E23E8713F6D1AD001287B6 /* rq_table.dat */; };
 		71E23EA813F6D1AD001287B6 /* sf_table.dat in Resources */ = {isa = PBXBuildFile; fileRef = 71E23E8813F6D1AD001287B6 /* sf_table.dat */; };
 		71E23EA913F6D1AD001287B6 /* stream.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E23E8913F6D1AD001287B6 /* stream.c */; };
-		71E23EAA13F6D1AD001287B6 /* stream.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E8A13F6D1AD001287B6 /* stream.h */; };
 		71E23EAB13F6D1AD001287B6 /* synth.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E23E8B13F6D1AD001287B6 /* synth.c */; };
-		71E23EAC13F6D1AD001287B6 /* synth.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E8C13F6D1AD001287B6 /* synth.h */; };
 		71E23EAD13F6D1AD001287B6 /* timer.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E23E8D13F6D1AD001287B6 /* timer.c */; };
-		71E23EAE13F6D1AD001287B6 /* timer.h in Copy Frameworks into .app bundle */ = {isa = PBXBuildFile; fileRef = 71E23E8E13F6D1AD001287B6 /* timer.h */; };
 		71E27E050D8C7E2F0048BA16 /* fight.c in Sources */ = {isa = PBXBuildFile; fileRef = 71E27E030D8C7E2F0048BA16 /* fight.c */; };
 		71F0F6D70DAA63B400F88C16 /* ending.c in Sources */ = {isa = PBXBuildFile; fileRef = 71F0F6D10DAA63B400F88C16 /* ending.c */; };
 		71F0F6D90DAA63B500F88C16 /* itemmenu.c in Sources */ = {isa = PBXBuildFile; fileRef = 71F0F6D30DAA63B400F88C16 /* itemmenu.c */; };
@@ -111,6 +94,12 @@
 		8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
 		8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
 		C602B0021CEF3E6C003A7B09 /* palcfg.c in Sources */ = {isa = PBXBuildFile; fileRef = C602B0011CEF3E6C003A7B09 /* palcfg.c */; };
+		C626003D1E62058F00E39DD9 /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = C626003C1E62058F00E39DD9 /* gtest-all.cc */; };
+		C62600401E620C7300E39DD9 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C626FFE61E6204BE00E39DD9 /* Info.plist */; };
+		C62600431E620CE700E39DD9 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C62600421E620CE700E39DD9 /* Cocoa.framework */; };
+		C62600451E620F0500E39DD9 /* GoogleTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = C62600441E620F0500E39DD9 /* GoogleTests.mm */; };
+		C62600481E6210B200E39DD9 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 717AE62F182663E100B10A11 /* SDL2.framework */; };
+		C62600491E62139C00E39DD9 /* test_swprintf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C62600461E62105500E39DD9 /* test_swprintf.cpp */; };
 		C65BC11D1CFAF1780037E9A2 /* overlay.c in Sources */ = {isa = PBXBuildFile; fileRef = C65BC11C1CFAF1780037E9A2 /* overlay.c */; };
 		C65BC11F1CFAF7790037E9A2 /* audio.c in Sources */ = {isa = PBXBuildFile; fileRef = C65BC11E1CFAF7790037E9A2 /* audio.c */; };
 /* End PBXBuildFile section */
@@ -122,23 +111,6 @@
 			dstPath = "";
 			dstSubfolderSpec = 10;
 			files = (
-				71E23E9213F6D1AD001287B6 /* bit.h in Copy Frameworks into .app bundle */,
-				71E23E9513F6D1AD001287B6 /* decoder.h in Copy Frameworks into .app bundle */,
-				71E23E9713F6D1AD001287B6 /* fixed.h in Copy Frameworks into .app bundle */,
-				71E23E9913F6D1AD001287B6 /* frame.h in Copy Frameworks into .app bundle */,
-				71E23E9B13F6D1AD001287B6 /* huffman.h in Copy Frameworks into .app bundle */,
-				71E23E9E13F6D1AD001287B6 /* layer3.h in Copy Frameworks into .app bundle */,
-				71E23EA013F6D1AD001287B6 /* layer12.h in Copy Frameworks into .app bundle */,
-				71E23EA113F6D1AD001287B6 /* libmad_config.h in Copy Frameworks into .app bundle */,
-				71E23EA213F6D1AD001287B6 /* libmad_global.h in Copy Frameworks into .app bundle */,
-				71E23EA313F6D1AD001287B6 /* mad.h in Copy Frameworks into .app bundle */,
-				71E23EA513F6D1AD001287B6 /* music_mad.h in Copy Frameworks into .app bundle */,
-				71E23EAA13F6D1AD001287B6 /* stream.h in Copy Frameworks into .app bundle */,
-				71E23EAC13F6D1AD001287B6 /* synth.h in Copy Frameworks into .app bundle */,
-				71E23EAE13F6D1AD001287B6 /* timer.h in Copy Frameworks into .app bundle */,
-				71147E4114085E31003FB2DB /* surroundopl.h in Copy Frameworks into .app bundle */,
-				7138FD0E1424E4810060DE76 /* demuopl.h in Copy Frameworks into .app bundle */,
-				7138FD101424E4810060DE76 /* dosbox_opl.h in Copy Frameworks into .app bundle */,
 			);
 			name = "Copy Frameworks into .app bundle";
 			runOnlyForDeploymentPostprocessing = 0;
@@ -300,7 +272,7 @@
 		7138FD0C1424E4810060DE76 /* dosbox_opl.cpp */ = {isa = PBXFileReference; fileEncoding = 18446744071562067968; lastKnownFileType = sourcecode.cpp.cpp; name = dosbox_opl.cpp; path = adplug/dosbox_opl.cpp; sourceTree = "<group>"; };
 		7138FD0D1424E4810060DE76 /* dosbox_opl.h */ = {isa = PBXFileReference; fileEncoding = 18446744071562067968; lastKnownFileType = sourcecode.c.h; name = dosbox_opl.h; path = adplug/dosbox_opl.h; sourceTree = "<group>"; };
 		716EB9C60D77340300D5DE1F /* sdlpal.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = sdlpal.icns; sourceTree = SOURCE_ROOT; };
-		717AE62F182663E100B10A11 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = ../../../../Library/Frameworks/SDL2.framework; sourceTree = "<group>"; };
+		717AE62F182663E100B10A11 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = ../../../../../Library/Frameworks/SDL2.framework; sourceTree = SOURCE_ROOT; };
 		71E23E7113F6D1AD001287B6 /* bit.c */ = {isa = PBXFileReference; fileEncoding = 0; lastKnownFileType = sourcecode.c.c; name = bit.c; path = libmad/bit.c; sourceTree = "<group>"; };
 		71E23E7213F6D1AD001287B6 /* bit.h */ = {isa = PBXFileReference; fileEncoding = 0; lastKnownFileType = sourcecode.c.h; name = bit.h; path = libmad/bit.h; sourceTree = "<group>"; };
 		71E23E7313F6D1AD001287B6 /* D.dat */ = {isa = PBXFileReference; fileEncoding = 0; lastKnownFileType = text; name = D.dat; path = libmad/D.dat; sourceTree = "<group>"; };
@@ -342,7 +314,14 @@
 		8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
 		8D1107320486CEB800E47090 /* Pal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pal.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		C602B0011CEF3E6C003A7B09 /* palcfg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = palcfg.c; sourceTree = "<group>"; };
+		C626003C1E62058F00E39DD9 /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "../../3rd/googletest/googletest/src/gtest-all.cc"; sourceTree = "<group>"; };
+		C626003E1E6206AA00E39DD9 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
+		C62600421E620CE700E39DD9 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
+		C62600441E620F0500E39DD9 /* GoogleTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GoogleTests.mm; sourceTree = "<group>"; };
+		C62600461E62105500E39DD9 /* test_swprintf.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = test_swprintf.cpp; path = ../../tests/test_swprintf.cpp; sourceTree = "<group>"; };
 		C626FFC31E5BD49100E39DD9 /* pal_config.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = pal_config.h; path = macos/pal_config.h; sourceTree = "<group>"; };
+		C626FFE21E6204BE00E39DD9 /* PalTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PalTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		C626FFE61E6204BE00E39DD9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		C65BC11C1CFAF1780037E9A2 /* overlay.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = overlay.c; sourceTree = "<group>"; };
 		C65BC11E1CFAF7790037E9A2 /* audio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = audio.c; sourceTree = "<group>"; };
 /* End PBXFileReference section */
@@ -357,6 +336,15 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		C626FFDF1E6204BE00E39DD9 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				C62600431E620CE700E39DD9 /* Cocoa.framework in Frameworks */,
+				C62600481E6210B200E39DD9 /* SDL2.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
@@ -382,6 +370,7 @@
 			isa = PBXGroup;
 			children = (
 				8D1107320486CEB800E47090 /* Pal.app */,
+				C626FFE21E6204BE00E39DD9 /* PalTests.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -394,6 +383,7 @@
 				7104FD990D772FAA00A97E53 /* adplug */,
 				29B97315FDCFA39411CA2CEA /* Other Sources */,
 				29B97317FDCFA39411CA2CEA /* Resources */,
+				C626FFE31E6204BE00E39DD9 /* PalTests */,
 				29B97323FDCFA39411CA2CEA /* Frameworks */,
 				19C28FACFE9D520D11CA2CBB /* Products */,
 			);
@@ -485,6 +475,8 @@
 		29B97323FDCFA39411CA2CEA /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				C62600421E620CE700E39DD9 /* Cocoa.framework */,
+				C626003E1E6206AA00E39DD9 /* XCTest.framework */,
 				1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
 				1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
 			);
@@ -714,6 +706,17 @@
 			path = ..;
 			sourceTree = "<group>";
 		};
+		C626FFE31E6204BE00E39DD9 /* PalTests */ = {
+			isa = PBXGroup;
+			children = (
+				C62600461E62105500E39DD9 /* test_swprintf.cpp */,
+				C626003C1E62058F00E39DD9 /* gtest-all.cc */,
+				C62600441E620F0500E39DD9 /* GoogleTests.mm */,
+				C626FFE61E6204BE00E39DD9 /* Info.plist */,
+			);
+			path = PalTests;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -736,6 +739,23 @@
 			productReference = 8D1107320486CEB800E47090 /* Pal.app */;
 			productType = "com.apple.product-type.application";
 		};
+		C626FFE11E6204BE00E39DD9 /* PalTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = C626FFE91E6204BE00E39DD9 /* Build configuration list for PBXNativeTarget "PalTests" */;
+			buildPhases = (
+				C626FFDE1E6204BE00E39DD9 /* Sources */,
+				C626FFDF1E6204BE00E39DD9 /* Frameworks */,
+				C626FFE01E6204BE00E39DD9 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = PalTests;
+			productName = PalTests;
+			productReference = C626FFE21E6204BE00E39DD9 /* PalTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
 /* End PBXNativeTarget section */
 
 /* Begin PBXProject section */
@@ -743,6 +763,13 @@
 			isa = PBXProject;
 			attributes = {
 				LastUpgradeCheck = 0820;
+				TargetAttributes = {
+					C626FFE11E6204BE00E39DD9 = {
+						CreatedOnToolsVersion = 8.2.1;
+						ProvisioningStyle = Automatic;
+						TestTargetID = 8D1107260486CEB800E47090;
+					};
+				};
 			};
 			buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Pal" */;
 			compatibilityVersion = "Xcode 3.2";
@@ -756,6 +783,7 @@
 			projectRoot = "";
 			targets = (
 				8D1107260486CEB800E47090 /* Pal */,
+				C626FFE11E6204BE00E39DD9 /* PalTests */,
 			);
 		};
 /* End PBXProject section */
@@ -775,6 +803,14 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		C626FFE01E6204BE00E39DD9 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				C62600401E620C7300E39DD9 /* Info.plist in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
@@ -865,6 +901,16 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		C626FFDE1E6204BE00E39DD9 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				C626003D1E62058F00E39DD9 /* gtest-all.cc in Sources */,
+				C62600451E620F0500E39DD9 /* GoogleTests.mm in Sources */,
+				C62600491E62139C00E39DD9 /* test_swprintf.cpp in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXSourcesBuildPhase section */
 
 /* Begin PBXVariantGroup section */
@@ -920,8 +966,8 @@
 				GCC_MODEL_TUNING = G5;
 				HEADER_SEARCH_PATHS = (
 					/Library/Frameworks/SDL2.framework/Headers,
-					./liboggvorbis/include,
-					./liboggvorbis/src,
+					../liboggvorbis/include,
+					../liboggvorbis/src,
 					"$(HEADER_SEARCH_PATHS)",
 				);
 				INFOPLIST_FILE = Info.plist;
@@ -1002,6 +1048,105 @@
 			};
 			name = Release;
 		};
+		C626FFEA1E6204BE00E39DD9 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CODE_SIGN_IDENTITY = "-";
+				COMBINE_HIDPI_IMAGES = YES;
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				FRAMEWORK_SEARCH_PATHS = (
+					/Library/Frameworks,
+					"$(PLATFORM_DIR)/Developer/Library/Frameworks",
+					"$(FRAMEWORK_SEARCH_PATHS)",
+					"$(LOCAL_LIBRARY_DIR)/Frameworks",
+				);
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"UNIT_TEST=1",
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				HEADER_SEARCH_PATHS = (
+					../liboggvorbis/include,
+					../liboggvorbis/src,
+					../3rd/googletest/googletest/,
+					../3rd/googletest/googletest/include,
+					/Library/Frameworks/SDL2.framework/Headers,
+					"$(HEADER_SEARCH_PATHS)",
+				);
+				INFOPLIST_FILE = PalTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = user.PalTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Pal.app/Contents/MacOS/Pal";
+				USER_HEADER_SEARCH_PATHS = "/Library/Frameworks/SDL2.framework/Headers $(HEADER_SEARCH_PATHS)";
+			};
+			name = Debug;
+		};
+		C626FFEB1E6204BE00E39DD9 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CODE_SIGN_IDENTITY = "-";
+				COMBINE_HIDPI_IMAGES = YES;
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				FRAMEWORK_SEARCH_PATHS = (
+					/Library/Frameworks,
+					"$(PLATFORM_DIR)/Developer/Library/Frameworks",
+					"$(FRAMEWORK_SEARCH_PATHS)",
+					"$(LOCAL_LIBRARY_DIR)/Frameworks",
+				);
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_PREPROCESSOR_DEFINITIONS = "UNIT_TEST=1";
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				HEADER_SEARCH_PATHS = (
+					../liboggvorbis/include,
+					../liboggvorbis/src,
+					../3rd/googletest/googletest/,
+					../3rd/googletest/googletest/include,
+					/Library/Frameworks/SDL2.framework/Headers,
+					"$(HEADER_SEARCH_PATHS)",
+				);
+				INFOPLIST_FILE = PalTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				PRODUCT_BUNDLE_IDENTIFIER = user.PalTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Pal.app/Contents/MacOS/Pal";
+				USER_HEADER_SEARCH_PATHS = "/Library/Frameworks/SDL2.framework/Headers $(HEADER_SEARCH_PATHS)";
+			};
+			name = Release;
+		};
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
@@ -1023,6 +1168,15 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		C626FFE91E6204BE00E39DD9 /* Build configuration list for PBXNativeTarget "PalTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				C626FFEA1E6204BE00E39DD9 /* Debug */,
+				C626FFEB1E6204BE00E39DD9 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 /* End XCConfigurationList section */
 	};
 	rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;

+ 264 - 0
macos/PalTests/GoogleTests.mm

@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2013 Matthew Stevens
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#import <Foundation/Foundation.h>
+#import <XCTest/XCTest.h>
+#import <gtest/gtest.h>
+#import <objc/runtime.h>
+
+using testing::TestCase;
+using testing::TestInfo;
+using testing::TestPartResult;
+using testing::UnitTest;
+
+static NSString * const GoogleTestDisabledPrefix = @"DISABLED_";
+
+/**
+ * Class prefix used for generated Objective-C class names.
+ *
+ * If a class name generated for a Google Test case conflicts with an existing
+ * class the value of this variable can be changed to add a class prefix.
+ */
+static NSString * const GeneratedClassPrefix = @"";
+
+/**
+ * Map of test keys to Google Test filter strings.
+ *
+ * Some names allowed by Google Test would result in illegal Objective-C
+ * identifiers and in such cases the generated class and method names are
+ * adjusted to handle this. This map is used to obtain the original Google Test
+ * filter string associated with a generated Objective-C test method.
+ */
+static NSDictionary *GoogleTestFilterMap;
+
+/**
+ * A Google Test listener that reports failures to XCTest.
+ */
+class XCTestListener : public testing::EmptyTestEventListener {
+public:
+    XCTestListener(XCTestCase *testCase) :
+    _testCase(testCase) {}
+    
+    void OnTestPartResult(const TestPartResult& test_part_result) {
+        if (test_part_result.passed())
+            return;
+        
+        int lineNumber = test_part_result.line_number();
+        const char *fileName = test_part_result.file_name();
+        NSString *path = fileName ? [@(fileName) stringByStandardizingPath] : nil;
+        NSString *description = @(test_part_result.message());
+        [_testCase recordFailureWithDescription:description
+                                         inFile:path
+                                         atLine:(lineNumber >= 0 ? (NSUInteger)lineNumber : 0)
+                                       expected:YES];
+    }
+    
+private:
+    XCTestCase *_testCase;
+};
+
+/**
+ * Registers an XCTestCase subclass for each Google Test case.
+ *
+ * Generating these classes allows Google Test cases to be represented as peers
+ * of standard XCTest suites and supports filtering of test runs to specific
+ * Google Test cases or individual tests via Xcode.
+ */
+@interface GoogleTestLoader : NSObject
+@end
+
+/**
+ * Base class for the generated classes for Google Test cases.
+ */
+@interface GoogleTestCase : XCTestCase
+@end
+
+@implementation GoogleTestCase
+
+/**
+ * Associates generated Google Test classes with the test bundle.
+ *
+ * This affects how the generated test cases are represented in reports. By
+ * associating the generated classes with a test bundle the Google Test cases
+ * appear to be part of the same test bundle that this source file is compiled
+ * into. Without this association they appear to be part of a bundle
+ * representing the directory of an internal Xcode tool that runs the tests.
+ */
++ (NSBundle *)bundleForClass {
+    return [NSBundle bundleForClass:[GoogleTestLoader class]];
+}
+
+/**
+ * Implementation of +[XCTestCase testInvocations] that returns an array of test
+ * invocations for each test method in the class.
+ *
+ * This differs from the standard implementation of testInvocations, which only
+ * adds methods with a prefix of "test".
+ */
++ (NSArray *)testInvocations {
+    NSMutableArray *invocations = [NSMutableArray array];
+    
+    unsigned int methodCount = 0;
+    Method *methods = class_copyMethodList([self class], &methodCount);
+    
+    for (unsigned int i = 0; i < methodCount; i++) {
+        SEL sel = method_getName(methods[i]);
+        NSMethodSignature *sig = [self instanceMethodSignatureForSelector:sel];
+        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+        [invocation setSelector:sel];
+        [invocations addObject:invocation];
+    }
+    
+    free(methods);
+    
+    return invocations;
+}
+
+@end
+
+/**
+ * Runs a single test.
+ */
+static void RunTest(id self, SEL _cmd) {
+    XCTestListener *listener = new XCTestListener(self);
+    UnitTest *googleTest = UnitTest::GetInstance();
+    googleTest->listeners().Append(listener);
+    
+    NSString *testKey = [NSString stringWithFormat:@"%@.%@", [self class], NSStringFromSelector(_cmd)];
+    NSString *testFilter = GoogleTestFilterMap[testKey];
+    XCTAssertNotNil(testFilter, @"No test filter found for test %@", testKey);
+    
+    testing::GTEST_FLAG(filter) = [testFilter UTF8String];
+    
+    (void)RUN_ALL_TESTS();
+    
+    delete googleTest->listeners().Release(listener);
+    
+    int totalTestsRun = googleTest->successful_test_count() + googleTest->failed_test_count();
+    XCTAssertEqual(totalTestsRun, 1, @"Expected to run a single test for filter \"%@\"", testFilter);
+}
+
+@implementation GoogleTestLoader
+
+/**
+ * Performs registration of classes for Google Test cases after our bundle has
+ * finished loading.
+ *
+ * This registration needs to occur before XCTest queries the runtime for test
+ * subclasses, but after C++ static initializers have run so that all Google
+ * Test cases have been registered. This is accomplished by synchronously
+ * observing the NSBundleDidLoadNotification for our own bundle.
+ */
++ (void)load {
+    NSBundle *bundle = [NSBundle bundleForClass:self];
+    [[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification object:bundle queue:nil usingBlock:^(NSNotification *notification) {
+        [self registerTestClasses];
+    }];
+}
+
++ (void)registerTestClasses {
+    // Pass the command-line arguments to Google Test to support the --gtest options
+    NSArray *arguments = [[NSProcessInfo processInfo] arguments];
+    
+    int i = 0;
+    int argc = (int)[arguments count];
+    const char **argv = (const char **)calloc((unsigned int)argc + 1, sizeof(const char *));
+    for (NSString *arg in arguments) {
+        argv[i++] = [arg UTF8String];
+    }
+    
+    testing::InitGoogleTest(&argc, (char **)argv);
+    UnitTest *googleTest = UnitTest::GetInstance();
+    testing::TestEventListeners& listeners = googleTest->listeners();
+    delete listeners.Release(listeners.default_result_printer());
+    free(argv);
+    
+    BOOL runDisabledTests = testing::GTEST_FLAG(also_run_disabled_tests);
+    NSMutableDictionary *testFilterMap = [NSMutableDictionary dictionary];
+    NSCharacterSet *decimalDigitCharacterSet = [NSCharacterSet decimalDigitCharacterSet];
+    
+    for (int testCaseIndex = 0; testCaseIndex < googleTest->total_test_case_count(); testCaseIndex++) {
+        const TestCase *testCase = googleTest->GetTestCase(testCaseIndex);
+        NSString *testCaseName = @(testCase->name());
+        
+        // For typed tests '/' is used to separate the parts of the test case name.
+        NSArray *testCaseNameComponents = [testCaseName componentsSeparatedByString:@"/"];
+        
+        if (runDisabledTests == NO) {
+            BOOL testCaseDisabled = NO;
+            
+            for (NSString *component in testCaseNameComponents) {
+                if ([component hasPrefix:GoogleTestDisabledPrefix]) {
+                    testCaseDisabled = YES;
+                    break;
+                }
+            }
+            
+            if (testCaseDisabled) {
+                continue;
+            }
+        }
+        
+        // Join the test case name components with '_' rather than '/' to create
+        // a valid class name.
+        NSString *className = [GeneratedClassPrefix stringByAppendingString:[testCaseNameComponents componentsJoinedByString:@"_"]];
+        
+        Class testClass = objc_allocateClassPair([GoogleTestCase class], [className UTF8String], 0);
+        NSAssert1(testClass, @"Failed to register Google Test class \"%@\", this class may already exist. The value of GeneratedClassPrefix can be changed to avoid this.", className);
+        BOOL hasMethods = NO;
+        
+        for (int testIndex = 0; testIndex < testCase->total_test_count(); testIndex++) {
+            const TestInfo *testInfo = testCase->GetTestInfo(testIndex);
+            NSString *testName = @(testInfo->name());
+            if (runDisabledTests == NO && [testName hasPrefix:GoogleTestDisabledPrefix]) {
+                continue;
+            }
+            
+            // Google Test allows test names starting with a digit, prefix these with an
+            // underscore to create a valid method name.
+            NSString *methodName = testName;
+            if ([methodName length] > 0 && [decimalDigitCharacterSet characterIsMember:[methodName characterAtIndex:0]]) {
+                methodName = [@"_" stringByAppendingString:methodName];
+            }
+            
+            NSString *testKey = [NSString stringWithFormat:@"%@.%@", className, methodName];
+            NSString *testFilter = [NSString stringWithFormat:@"%@.%@", testCaseName, testName];
+            testFilterMap[testKey] = testFilter;
+            
+            SEL selector = sel_registerName([methodName UTF8String]);
+            BOOL added = class_addMethod(testClass, selector, (IMP)RunTest, "v@:");
+            NSAssert1(added, @"Failed to add Goole Test method \"%@\", this method may already exist in the class.", methodName);
+            hasMethods = YES;
+        }
+        
+        if (hasMethods) {
+            objc_registerClassPair(testClass);
+        } else {
+            objc_disposeClassPair(testClass);
+        }
+    }
+    
+    GoogleTestFilterMap = testFilterMap;
+}
+
+@end

+ 22 - 0
macos/PalTests/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>en</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>

+ 3 - 0
main.c

@@ -483,6 +483,7 @@ PAL_SplashScreen(
    PAL_FadeOut(1);
 }
 
+#ifndef UNIT_TEST
 int
 main(
    int      argc,
@@ -563,3 +564,5 @@ main(
    assert(FALSE);
    return 255;
 }
+#endif //UNIT_TEST
+

+ 26 - 0
tests/test_swprintf.cpp

@@ -0,0 +1,26 @@
+#include <gtest/gtest.h>
+extern "C"{
+    #include "common.h"
+    #include "palcommon.h"
+    #include "text.h"
+}
+
+#define swprintf_wrapper( buffer, count, format, ...) ( \
+PAL_swprintf( buffer, count, format, ##__VA_ARGS__ ), \
+buffer \
+)
+
+TEST(sdlpal, PAL_swprintf) {
+    WCHAR test_buf[256];
+    EXPECT_EQ(0, wcsncmp( L"测试",    swprintf_wrapper( test_buf, sizeof(test_buf)/sizeof(WCHAR), L"%ls%ls", L"测", L"试"), wcslen(L"测试")));
+}
+
+TEST(sdlpal, PAL_swprintf2) {
+    WCHAR test_buf[256];
+    EXPECT_EQ(0, wcsncmp( L"测试 2",  swprintf_wrapper( test_buf, sizeof(test_buf)/sizeof(WCHAR), L"%ls%ls %d", L"测", L"试", 2), wcslen(L"测试 2")));
+}
+
+TEST(sdlpal, PAL_swprintf3) {
+    WCHAR test_buf[256];
+    EXPECT_EQ(0, wcsncmp( L"测试 3",  swprintf_wrapper( test_buf, sizeof(test_buf)/sizeof(WCHAR), L"%ls%ls %c", L"测", L"试", '3'), wcslen(L"测试 3")));
+}

+ 6 - 0
tests/testmain.cpp

@@ -0,0 +1,6 @@
+#include <gtest/gtest.h>
+
+int main(int argc, char *argv[]) {
+    testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}

+ 36 - 6
unix/Makefile

@@ -1,26 +1,56 @@
 # General makefile for generic unix & linux platforms
 
 TARGET = sdlpal
+TEST_TARGET = ./sdlpal-tests
 
 HOST =
 
 CFILES = $(wildcard ../adplug/*.c) $(wildcard ../libmad/*.c) $(wildcard ../liboggvorbis/src/*.c) $(wildcard ../*.c)
+CFILES := $(filter-out ../main.c, $(CFILES))
 CPPFILES = $(wildcard ../adplug/*.cpp) $(wildcard ../*.cpp) $(wildcard ./*.cpp)
 OBJFILES = $(CFILES:.c=.o) $(CPPFILES:.cpp=.o)
 
-CCFLAGS = `sdl2-config --cflags` -g -Wall -O2 -fno-strict-aliasing -I. -I../ -I../liboggvorbis/include -I../liboggvorbis/src -DPAL_HAS_PLATFORM_SPECIFIC_UTILS
-CXXFLAGS = $(CCFLAGS) -std=c++11 `fltk-config --cxxflags`
+TEST_CPPFILES = $(wildcard ../tests/*.cpp)
+TEST_OBJFILES = $(TEST_CPPFILES:.cpp=.o)
+
+GTEST_DIR = ../3rd/googletest/googletest
+GTEST_HEADERS = $(GTEST_DIR)/include/gtest/*.h \
+                $(GTEST_DIR)/include/gtest/internal/*.h
+
+GTEST_SRCS_ = $(GTEST_DIR)/src/*.cc $(GTEST_DIR)/src/*.h $(GTEST_HEADERS)
+
+TEST_CPPFLAGS += -isystem $(GTEST_DIR)/include
+TEST_CXXFLAGS += -g -Wall -Wextra -pthread
+
+CCFLAGS = `sdl2-config --cflags` -g -Wall -O2 -fno-strict-aliasing -I. -I../ -I../liboggvorbis/include -I../liboggvorbis/src -DPAL_HAS_PLATFORM_SPECIFIC_UTILS -I$(GTEST_DIR)
+CXXFLAGS = $(CCFLAGS) -std=c++11 `fltk-config --cxxflags` $(TEST_CPPFLAGS) $(TEST_CXXFLAGS)
 CFLAGS = $(CCFLAGS) -std=gnu99 `fltk-config --cflags`
 LDFLAGS = `sdl2-config --libs` `fltk-config --ldflags` -lstdc++ -lm
 
-$(TARGET): $(OBJFILES)
-	$(HOST)g++ $(OBJFILES) -o $(TARGET) $(LDFLAGS)
+all: $(TARGET)
+
+gtest-all.o : $(GTEST_SRCS_)
+	$(CXX) $(CXXFLAGS) -c \
+            $(GTEST_DIR)/src/gtest-all.cc
+
+$(TARGET): $(OBJFILES) ../main.o
+	$(HOST)g++ $^ -o $@ $(LDFLAGS)
+
+$(TEST_TARGET): $(OBJFILES) $(TEST_OBJFILES) main-test.o gtest-all.o
+	$(HOST)g++ $^ -o $@ $(LDFLAGS)
 
 %.o: %.c
 	$(HOST)gcc $(CFLAGS) -c $< -o $@
 
 %.o: %.cpp
-	$(HOST)gcc $(CXXFLAGS) -c $< -o $@
+	$(HOST)g++ $(CXXFLAGS) -c $< -o $@
+
+main-test.o: ../main.c
+	$(HOST)gcc $(CFLAGS) -DUNIT_TEST=1 -c $< -o $@
 
 clean:
-	-rm -f $(TARGET) *.o ../*.o ../adplug/*.o ../libmad/*.o ../liboggvorbis/src/*.o
+	-rm -f $(TARGET) $(TEST_TARGET) *.o ../*.o ../adplug/*.o ../libmad/*.o ../liboggvorbis/src/*.o
+
+check: $(TEST_TARGET)
+	chmod +x $(TEST_TARGET)
+	exec $(TEST_TARGET)