Browse Source

Merge pull request #6 from sdlpal/unittest

Google Test Integration
Pal Lockheart 7 years ago
parent
commit
2ffebe310b

+ 1 - 0
.gitignore

@@ -23,6 +23,7 @@ bld/
 [Oo]bj/
 Generated Files/
 BundleArtifacts/
+[Tt]est/
 *.o
 *.bc
 *.data

+ 3 - 0
.gitmodules

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

+ 4 - 2
.travis.yml

@@ -1,7 +1,9 @@
 dist: trusty
 language: c
-script: make
-compiler: clang
+script:
+ - make
+ - make clean
+ - make check
 before_install:
  - sudo apt-get update -qq
  - sudo apt-get install -qq libsdl2-dev libfltk1.3-dev

+ 1 - 0
3rd/googletest

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

+ 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>

+ 7 - 1
main.c

@@ -525,6 +525,7 @@ main(
 
    UTIL_OpenLog();
 
+#if !defined(UNIT_TEST) || defined(UNIT_TEST_GAME_INIT)
    PAL_LoadConfig(TRUE);
 
    //
@@ -540,12 +541,13 @@ main(
    //
    if (gConfig.fLaunchSetting)
 	   return 0;
-
    //
    // Initialize everything
    //
    PAL_Init();
+#endif
 
+#if !defined(UNIT_TEST)
    //
    // Show the trademark screen and splash screen
    //
@@ -562,4 +564,8 @@ main(
    //
    assert(FALSE);
    return 255;
+#else
+   extern int testmain(int argc, char *argv[]);
+   return testmain(argc, argv);
+#endif
 }

+ 50 - 0
tests/README.md

@@ -0,0 +1,50 @@
+How to write unit tests
+=======================
+
+***SDLPAL*** adopts the [Google C++ testing framework](https://github.com/google/googletest) for unit tests.
+
+Writing a unit test using Google C++ testing framework is easy as 1-2-3:
+
+
+Step 1
+------
+
+Create a new c++ code file containing test codes in the **`tests`** directory, including necessary header files such that the stuff your test logic needs is declared. If you dont't know which appropriate header to be included, just follow the code snippet here, which should be sufficient in most cases: 
+
+```c++
+// Include the Google C++ testing framework
+#include <gtest/gtest.h>
+// Inlucde the functions to be tested
+extern "C"{
+    #include "main.h"
+}
+```
+
+### Notes for file naming convention
+
+To avoid any potential file naming conflicts, please name your test code files starting as ***test_***.
+
+
+Step 2
+------
+
+Use the **`TEST`** macro to define your tests. **`TEST`** has two parameters: the first is test case name and the second is the test name. After using the macro, you should define your test logic between a pair of braces. You can use a bunch of macros such as ***EXPECT_EQ*** to indicate the success or failure of a test. The following code snippet demonstrates how to create a test named ***PALswprintf*** in test case ***sdlpal*** to test the ***`PAL_swprintf`*** function:
+
+```c++
+TEST(sdlpal, PALswprintf) {
+    WCHAR test_buf[256];
+    EXPECT_EQ(0, wcsncmp( L"测试",    test_buf, PAL_swprintf(test_buf, sizeof(test_buf)/sizeof(WCHAR), L"%ls%ls", L"测", L"试")));
+}
+```
+
+As tests are grouped into test cases in Google Test, please put logically related tests into the same test case. The test case name and the test name should both be valid C++ identifiers, and you should avoid to use underscore (_) in these names. Google Test guarantees that each test you define is run exactly once, but it makes no guarantee on the order the tests are executed. Therefore, you should write your tests in such a way that their results don't depend on their order.
+
+
+Step 3
+------
+
+When you finished writting test codes, you can use ***`make check`*** to compile and run tests. If you are using ***Visual Studio*** under Windows, please make sure you've actived the **Test** configuration before you launch the program.
+
+### Special notes for ***Visual Studio*** users
+
+You should put all test files into the ***Test Files*** filter, and make sure you've excluded them for compilation in configurations other than **Test**.

+ 8 - 0
tests/test_main.cpp

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

+ 21 - 0
tests/test_swprintf.cpp

@@ -0,0 +1,21 @@
+#include <gtest/gtest.h>
+extern "C"{
+    #include "common.h"
+    #include "palcommon.h"
+    #include "text.h"
+}
+
+TEST(sdlpal, PALswprintf) {
+    WCHAR test_buf[256];
+    EXPECT_EQ(0, wcsncmp( L"测试",    test_buf, PAL_swprintf(test_buf, sizeof(test_buf)/sizeof(WCHAR), L"%ls%ls", L"测", L"试")));
+}
+
+TEST(sdlpal, PALswprintf2) {
+    WCHAR test_buf[256];
+    EXPECT_EQ(0, wcsncmp( L"测试 2",  test_buf, PAL_swprintf(test_buf, sizeof(test_buf)/sizeof(WCHAR), L"%ls%ls %d", L"测", L"试", 2)));
+}
+
+TEST(sdlpal, PALswprintf3) {
+    WCHAR test_buf[256];
+    EXPECT_EQ(0, wcsncmp( L"测试 3",  test_buf, PAL_swprintf(test_buf, sizeof(test_buf)/sizeof(WCHAR), L"%ls%ls %c", L"测", L"试", '3')));
+}

+ 33 - 5
unix/Makefile

@@ -1,26 +1,54 @@
 # General makefile for generic unix & linux platforms
 
 TARGET = sdlpal
+TEST_TARGET = ./sdlpal-tests
 
 HOST =
+TEST_CCFLAGS =
+
+GTEST_DIR = ../3rd/googletest/googletest
 
 CFILES = $(wildcard ../adplug/*.c) $(wildcard ../libmad/*.c) $(wildcard ../liboggvorbis/src/*.c) $(wildcard ../*.c)
 CPPFILES = $(wildcard ../adplug/*.cpp) $(wildcard ../*.cpp) $(wildcard ./*.cpp)
 OBJFILES = $(CFILES:.c=.o) $(CPPFILES:.cpp=.o)
+TEST_CPPFILES = $(wildcard ../tests/*.cpp)
+TEST_OBJFILES = $(TEST_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
+CCFLAGS = `sdl2-config --cflags` -g -Wall -O2 -fno-strict-aliasing -I. -I../ -I../liboggvorbis/include -I../liboggvorbis/src -DPAL_HAS_PLATFORM_SPECIFIC_UTILS $(TEST_CCFLAGS)
 CXXFLAGS = $(CCFLAGS) -std=c++11 `fltk-config --cxxflags`
 CFLAGS = $(CCFLAGS) -std=gnu99 `fltk-config --cflags`
 LDFLAGS = `sdl2-config --libs` `fltk-config --ldflags` -lstdc++ -lm
+TEST_CXXFLAGS += -isystem $(GTEST_DIR)/include -I $(GTEST_DIR) -g -Wall -Wextra -pthread
+
+.PHONY : all clean check
+
+all: $(TARGET)
 
 $(TARGET): $(OBJFILES)
-	$(HOST)g++ $(OBJFILES) -o $(TARGET) $(LDFLAGS)
+	@echo [LD] $@
+	@$(HOST)gcc $^ -o $@ $(LDFLAGS)
+
+gtest-all.o : $(GTEST_DIR)/src/gtest-all.cc
+	@echo [CC] $^
+	@$(HOST)g++ $(TEST_CXXFLAGS) -c $< -o $@
 
 %.o: %.c
-	$(HOST)gcc $(CFLAGS) -c $< -o $@
+	@echo [CC] $^
+	@$(HOST)gcc $(CFLAGS) -c $< -o $@
 
 %.o: %.cpp
-	$(HOST)gcc $(CXXFLAGS) -c $< -o $@
+	@echo [CC] $^
+	@$(HOST)g++ $(CXXFLAGS) -c $< -o $@
+
+$(TEST_TARGET): $(OBJFILES) $(TEST_OBJFILES) gtest-all.o
+	@echo [LD] $@
+	@$(HOST)g++ $^ -o $@ $(LDFLAGS) -lpthread
 
 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 ../tests/*.o
+
+check: TEST_CCFLAGS = -DUNIT_TEST=1 -isystem $(GTEST_DIR)/include
+check: $(TEST_TARGET)
+	@echo [EXEC] $(TEST_TARGET)
+	@chmod +x $(TEST_TARGET)
+	@exec $(TEST_TARGET)

+ 1 - 1
unix/unix.cpp

@@ -244,7 +244,7 @@ UTIL_Platform_Init(
    char* argv[]
 )
 {
-#ifndef PAL_NO_LAUNCH_UI
+#if !defined(UNIT_TEST) && !defined(PAL_NO_LAUNCH_UI)
    if (gConfig.fLaunchSetting)
    {
       Fl_Window *window = InitWindow();

+ 39 - 10
win32/Makefile

@@ -1,30 +1,59 @@
 # Makefile for win32 platforms with mingw
 
 TARGET = sdlpal.exe
+TEST_TARGET = ./sdlpal-tests
 
 HOST =
+TEST_CCFLAGS =
+
+GTEST_DIR = ../3rd/googletest/googletest
 
 CFILES = $(wildcard ../adplug/*.c) $(wildcard ../libmad/*.c) $(wildcard ../liboggvorbis/src/*.c) $(wildcard ../*.c) ../native_midi/native_midi_win32.c ../native_midi/native_midi_common.c
 CPPFILES = $(wildcard ../adplug/*.cpp) $(wildcard ../*.cpp) $(wildcard ./*.cpp)
-OBJFILES = $(CFILES:.c=.o) $(CPPFILES:.cpp=.o) sdlpal.o
+RCFILES = $(wildcard ./*.rc)
+OBJFILES = $(CFILES:.c=.o) $(CPPFILES:.cpp=.o) $(RCFILES:.rc=.o)
+TEST_CPPFILES = $(wildcard ../tests/*.cpp)
+TEST_OBJFILES = $(TEST_CPPFILES:.cpp=.o)
 
-override CCFLAGS += `sdl2-config --cflags` -msse -Wall -O2 -fno-strict-aliasing -I../ -I../liboggvorbis/include -I../liboggvorbis/src -DPAL_HAS_PLATFORM_SPECIFIC_UTILS
-CXXFLAGS = $(CCFLAGS) -std=c++11
+override CCFLAGS = `sdl2-config --cflags` -g -msse -Wall -O2 -fno-strict-aliasing -I. -I../ -I../liboggvorbis/include -I../liboggvorbis/src -DPAL_HAS_PLATFORM_SPECIFIC_UTILS $(TEST_CCFLAGS)
+CXXFLAGS = $(CCFLAGS) -std=c++11 
 CFLAGS = $(CCFLAGS) -std=gnu99
 LDFLAGS = `sdl2-config --libs` -lm -lwinmm -lole32 -loleaut32 -limm32 -lcomctl32 -luuid -ldxguid -lversion -static -static-libgcc -static-libstdc++
+TEST_CXXFLAGS += -isystem $(GTEST_DIR)/include -I $(GTEST_DIR) -g -Wall -Wextra -pthread
+
+.PHONY : all clean check
+
+all: $(TARGET)
 
 $(TARGET): $(OBJFILES)
-	$(HOST)g++ $(OBJFILES) -o $(TARGET) $(LDFLAGS)
-	$(HOST)strip $(TARGET)
+	@echo [LD] $@
+	@$(HOST)gcc $^ -o $@ $(LDFLAGS)
+
+gtest-all.o : $(GTEST_DIR)/src/gtest-all.cc
+	@echo [CC] $^
+	@$(HOST)g++ $(TEST_CXXFLAGS) -c $^ -o $@
 
 %.o: %.c
-	$(HOST)gcc $(CFLAGS) -c $< -o $@
+	@echo [CC] $^
+	@$(HOST)gcc $(CFLAGS) -c $^ -o $@
 
 %.o: %.cpp
-	$(HOST)g++ $(CXXFLAGS) -c $< -o $@
-	
+	@echo [CC] $^
+	@$(HOST)g++ $(CXXFLAGS) -c $^ -o $@
+
 %.o: %.rc
-	windres -i $< -o $@
+	@echo [RES] $^
+	@windres -i $^ -o $@
+
+$(TEST_TARGET): $(OBJFILES) $(TEST_OBJFILES) gtest-all.o
+	@echo [LD] $@
+	@$(HOST)g++ $^ -o $@ $(LDFLAGS) -lpthread
 
 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 ../tests/*.o ../native_midi/*.o
+
+check: TEST_CCFLAGS = -DUNIT_TEST=1 -isystem $(GTEST_DIR)/include
+check: $(TEST_TARGET)
+	@echo [EXEC] $(TEST_TARGET)
+	@chmod +x $(TEST_TARGET)
+	@exec $(TEST_TARGET)

+ 7 - 1
win32/sdlpal.sln

@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 14
-VisualStudioVersion = 14.0.25123.0
+VisualStudioVersion = 14.0.25420.1
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sdlpal", "sdlpal.vcxproj", "{837BDF47-9375-4C30-866B-07E262E94A01}"
 EndProject
@@ -11,6 +11,8 @@ Global
 		Debug|x64 = Debug|x64
 		Release|Win32 = Release|Win32
 		Release|x64 = Release|x64
+		Test|Win32 = Test|Win32
+		Test|x64 = Test|x64
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{837BDF47-9375-4C30-866B-07E262E94A01}.Debug|Win32.ActiveCfg = Debug|Win32
@@ -21,6 +23,10 @@ Global
 		{837BDF47-9375-4C30-866B-07E262E94A01}.Release|Win32.Build.0 = Release|Win32
 		{837BDF47-9375-4C30-866B-07E262E94A01}.Release|x64.ActiveCfg = Release|x64
 		{837BDF47-9375-4C30-866B-07E262E94A01}.Release|x64.Build.0 = Release|x64
+		{837BDF47-9375-4C30-866B-07E262E94A01}.Test|Win32.ActiveCfg = Test|Win32
+		{837BDF47-9375-4C30-866B-07E262E94A01}.Test|Win32.Build.0 = Test|Win32
+		{837BDF47-9375-4C30-866B-07E262E94A01}.Test|x64.ActiveCfg = Test|x64
+		{837BDF47-9375-4C30-866B-07E262E94A01}.Test|x64.Build.0 = Test|x64
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 126 - 6
win32/sdlpal.vcxproj

@@ -17,6 +17,14 @@
       <Configuration>Release</Configuration>
       <Platform>x64</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Test|Win32">
+      <Configuration>Test</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Test|x64">
+      <Configuration>Test</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
   </ItemGroup>
   <PropertyGroup Label="Globals">
     <ProjectGuid>{837BDF47-9375-4C30-866B-07E262E94A01}</ProjectGuid>
@@ -28,11 +36,21 @@
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v120_xp</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Test|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v120_xp</PlatformToolset>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v120_xp</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Test|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v120_xp</PlatformToolset>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
@@ -50,10 +68,18 @@
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Test|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Test|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
@@ -68,16 +94,26 @@
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Test|Win32'">true</LinkIncremental>
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental>
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Test|x64'">true</LinkIncremental>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <IncludePath>..\SDL2\include;$(IncludePath)</IncludePath>
     <LibraryPath>..\SDL2\VisualC\$(Platform)\$(Configuration)\;..\SDL2\lib\x86;$(LibraryPath)</LibraryPath>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Test|Win32'">
+    <IncludePath>..\SDL2\include;..\3rd\googletest\googletest\include;$(IncludePath)</IncludePath>
+    <LibraryPath>..\SDL2\VisualC\$(Platform)\Debug\;..\SDL2\lib\x86;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <IncludePath>..\SDL2\include;$(IncludePath)</IncludePath>
     <LibraryPath>..\SDL2\VisualC\$(Platform)\$(Configuration)\;..\SDL2\lib\x64;$(LibraryPath)</LibraryPath>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Test|x64'">
+    <IncludePath>..\SDL2\include;..\3rd\googletest\googletest\include;$(IncludePath)</IncludePath>
+    <LibraryPath>..\SDL2\VisualC\$(Platform)\Debug\;..\SDL2\lib\x64;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <IncludePath>..\SDL2\include;$(IncludePath)</IncludePath>
     <LibraryPath>..\SDL2\VisualC\$(Platform)\$(Configuration)\;..\SDL2\lib\x86;$(LibraryPath)</LibraryPath>
@@ -97,7 +133,7 @@
       </HeaderFileName>
     </Midl>
     <ClCompile>
-      <AdditionalIncludeDirectories>.\;..\liboggvorbis\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\;.\;..\liboggvorbis\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>PAL_HAS_PLATFORM_SPECIFIC_UTILS;WIN32;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <StringPooling>true</StringPooling>
       <FunctionLevelLinking>true</FunctionLevelLinking>
@@ -126,7 +162,7 @@
       </HeaderFileName>
     </Midl>
     <ClCompile>
-      <AdditionalIncludeDirectories>.\;..\liboggvorbis\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\;.\;..\liboggvorbis\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>PAL_HAS_PLATFORM_SPECIFIC_UTILS;WIN32;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <StringPooling>true</StringPooling>
       <FunctionLevelLinking>true</FunctionLevelLinking>
@@ -155,8 +191,8 @@
       </HeaderFileName>
     </Midl>
     <ClCompile>
-      <AdditionalIncludeDirectories>.\;..\liboggvorbis\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>PAL_HAS_PLATFORM_SPECIFIC_UTILS;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>..\;.\;..\liboggvorbis\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>PAL_HAS_PLATFORM_SPECIFIC_UTILS;WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <MinimalRebuild>true</MinimalRebuild>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -176,6 +212,39 @@
     </Link>
     <Bscmake />
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Test|Win32'">
+    <Midl>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <MkTypLibCompatible>true</MkTypLibCompatible>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TargetEnvironment>Win32</TargetEnvironment>
+      <TypeLibraryName>.\Debug/sdlpal_sdl2.tlb</TypeLibraryName>
+      <HeaderFileName>
+      </HeaderFileName>
+    </Midl>
+    <ClCompile>
+      <AdditionalIncludeDirectories>..\;.\;..\liboggvorbis\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>PAL_HAS_PLATFORM_SPECIFIC_UTILS;WIN32;_WINDOWS;_DEBUG;UNIT_TEST;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <MinimalRebuild>true</MinimalRebuild>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0804</Culture>
+    </ResourceCompile>
+    <Link>
+      <AdditionalDependencies>winmm.lib;sdl2.lib;sdl2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>
+      </AdditionalLibraryDirectories>
+      <SubSystem>Console</SubSystem>
+    </Link>
+    <Bscmake />
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <Midl>
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -186,8 +255,8 @@
       </HeaderFileName>
     </Midl>
     <ClCompile>
-      <AdditionalIncludeDirectories>.\;..\liboggvorbis\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>PAL_HAS_PLATFORM_SPECIFIC_UTILS;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>..\;.\;..\liboggvorbis\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>PAL_HAS_PLATFORM_SPECIFIC_UTILS;WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
       <WarningLevel>Level3</WarningLevel>
@@ -205,7 +274,46 @@
     </Link>
     <Bscmake />
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Test|x64'">
+    <Midl>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <MkTypLibCompatible>true</MkTypLibCompatible>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TypeLibraryName>.\Debug/sdlpal_sdl2.tlb</TypeLibraryName>
+      <HeaderFileName>
+      </HeaderFileName>
+    </Midl>
+    <ClCompile>
+      <AdditionalIncludeDirectories>..\;.\;..\liboggvorbis\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>PAL_HAS_PLATFORM_SPECIFIC_UTILS;WIN32;_WINDOWS;_DEBUG;UNIT_TEST;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0804</Culture>
+    </ResourceCompile>
+    <Link>
+      <AdditionalDependencies>winmm.lib;sdl2.lib;sdl2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <SubSystem>Console</SubSystem>
+      <AdditionalLibraryDirectories>
+      </AdditionalLibraryDirectories>
+    </Link>
+    <Bscmake />
+  </ItemDefinitionGroup>
   <ItemGroup>
+    <ClCompile Include="..\3rd\googletest\googletest\src\gtest-all.cc">
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Test|Win32'">..\3rd\googletest\googletest;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Test|x64'">..\3rd\googletest\googletest;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="..\adplug\adlibemu.c" />
     <ClCompile Include="..\adplug\dbemuopl.cpp" />
     <ClCompile Include="..\adplug\dbopl.cpp">
@@ -270,6 +378,18 @@
     <ClCompile Include="..\scene.c" />
     <ClCompile Include="..\script.c" />
     <ClCompile Include="..\sound.c" />
+    <ClCompile Include="..\tests\test_main.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+    </ClCompile>
+    <ClCompile Include="..\tests\test_swprintf.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="..\text.c" />
     <ClCompile Include="..\ui.c" />
     <ClCompile Include="..\uibattle.c" />

+ 15 - 0
win32/sdlpal.vcxproj.filters

@@ -54,6 +54,12 @@
     <Filter Include="platform">
       <UniqueIdentifier>{06f44698-1e02-440e-bc40-9858a46c2d8f}</UniqueIdentifier>
     </Filter>
+    <Filter Include="Test Files">
+      <UniqueIdentifier>{c04132fc-026a-4e64-892d-76834bbdaef8}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="Test Files\deps">
+      <UniqueIdentifier>{5f2a83a2-9824-4e9e-a352-8dc90c92fbde}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\battle.c">
@@ -311,6 +317,15 @@
     <ClCompile Include="..\audio.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\3rd\googletest\googletest\src\gtest-all.cc">
+      <Filter>Test Files\deps</Filter>
+    </ClCompile>
+    <ClCompile Include="..\tests\test_main.cpp">
+      <Filter>Test Files\deps</Filter>
+    </ClCompile>
+    <ClCompile Include="..\tests\test_swprintf.cpp">
+      <Filter>Test Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\ascii.h">