1 #ifdef _WIN32
2 	#pragma comment(lib, "Utilities.lib")
3 	#include <Windows.h>
4 	#include <Shlobj.h>
5 #else
6 	#include <sys/wait.h>
7 	#include <stdio.h>
8 	#include <execinfo.h>
9 	#include <signal.h>
10 	#include <stdlib.h>
11 	#include <unistd.h>
12 
13 	#define __stdcall
14 #endif
15 
16 #include <iostream>
17 #include <thread>
18 #include <vector>
19 #include <string>
20 #include <thread>
21 #include "../Utilities/FolderUtilities.h"
22 #include "../Utilities/SimpleLock.h"
23 #include "../Utilities/Timer.h"
24 #include "../Core/MessageManager.h"
25 #include "../Core/ControlManager.h"
26 #include "../Core/EmulationSettings.h"
27 
28 using namespace std;
29 
30 typedef void (__stdcall *NotificationListenerCallback)(ConsoleNotificationType);
31 
32 extern "C" {
33 	void __stdcall InitDll();
34 	void __stdcall SetFlags(uint64_t flags);
35 	void __stdcall InitializeEmu(const char* homeFolder, void*, void*, bool, bool, bool);
36 	void __stdcall SetControllerType(uint32_t port, ControllerType type);
37 	int __stdcall RunAutomaticTest(char* filename);
38 	int __stdcall RunRecordedTest(char* filename);
39 	void __stdcall Run();
40 	void __stdcall Stop();
41 	INotificationListener* __stdcall RegisterNotificationCallback(int32_t consoleId, NotificationListenerCallback callback);
42 }
43 
44 std::thread *runThread = nullptr;
45 std::atomic<int> testIndex;
46 vector<string> testFilenames;
47 vector<string> failedTests;
48 vector<int32_t> failedTestErrorCode;
49 SimpleLock lock;
50 Timer timer;
51 bool automaticTests = false;
52 
RunEmu()53 void RunEmu()
54 {
55 	try {
56 		Run();
57 	} catch(std::exception ex) {
58 
59 	}
60 }
61 
OnNotificationReceived(ConsoleNotificationType type)62 void __stdcall OnNotificationReceived(ConsoleNotificationType type)
63 {
64 	static int count = 0;
65 	if(type == ConsoleNotificationType::GameLoaded) {
66 		count++;
67 		if(count % 2 == 0) {
68 			//GameLoaded is fired twice because of how the test roms are coded, we want to start running the test on the 2nd time only
69 			runThread = new std::thread(RunEmu);
70 		}
71 	}
72 }
73 
RunTest()74 void RunTest()
75 {
76 	while(true) {
77 		lock.Acquire();
78 		size_t index = testIndex++;
79 		lock.Release();
80 
81 		if(index < testFilenames.size()) {
82 			string filepath = testFilenames[index];
83 			string filename = FolderUtilities::GetFilename(filepath, false);
84 
85 			string command;
86 			if(automaticTests) {
87 				#ifdef _WIN32
88 					command = "TestHelper.exe /autotest \"" + filepath + "\"";
89 				#else
90 					command = "./testhelper /autotest \"" + filepath + "\"";
91 				#endif
92 			} else {
93 				#ifdef _WIN32
94 					command = "TestHelper.exe /testrom \"" + filepath + "\"";
95 				#else
96 					command = "./testhelper /testrom \"" + filepath + "\"";
97 				#endif
98 			}
99 
100 			lock.Acquire();
101 			std::cout << std::to_string(index) << ") " << filename << std::endl;
102 			lock.Release();
103 
104 			int failedFrames = std::system(command.c_str());
105 			#ifdef __GNUC__
106 				failedFrames = WEXITSTATUS(failedFrames);
107 			#endif
108 
109 			if(failedFrames != 0) {
110 				//Test failed
111 				lock.Acquire();
112 				failedTests.push_back(filename);
113 				failedTestErrorCode.push_back(failedFrames);
114 				std::cout << "  ****  " << std::to_string(index) << ") " << filename << " failed (" << failedFrames << ")" << std::endl;
115 				lock.Release();
116 			}
117 		} else {
118 			break;
119 		}
120 	}
121 }
122 
123 #ifdef __GNUC__
handler(int sig)124 	void handler(int sig) {
125 		void *array[20];
126 		size_t size = backtrace(array, 20);
127 
128 		std::cout << "Error: signal: " << std::to_string(sig) << std::endl;
129 		backtrace_symbols_fd(array, size, STDERR_FILENO);
130 		exit(1);
131 	}
132 #endif
133 
main(int argc,char * argv[])134 int main(int argc, char* argv[])
135 {
136 	#ifdef _WIN32
137 		wchar_t path[MAX_PATH];
138 		SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path);
139 		string mesenFolder = FolderUtilities::CombinePath(utf8::utf8::encode(path), "Mesen");
140 	#else
141 		string mesenFolder = "/home/saitoh/Mesen";
142 		signal(SIGSEGV, handler);
143 	#endif
144 
145 	if(argc >= 3 && strcmp(argv[1], "/auto") == 0) {
146 		string romFolder = argv[2];
147 		testFilenames = FolderUtilities::GetFilesInFolder(romFolder, { ".nes" }, true);
148 		automaticTests = true;
149 	} else if(argc <= 2) {
150 		string testFolder;
151 		if(argc == 1) {
152 			testFolder = FolderUtilities::CombinePath(mesenFolder, "Tests");
153 		} else {
154 			testFolder = argv[1];
155 		}
156 		testFilenames = FolderUtilities::GetFilesInFolder(testFolder, { ".mtp" }, true);
157 		automaticTests = false;
158 	}
159 
160 	if(!testFilenames.empty()) {
161 		vector<std::thread*> testThreads;
162 		testIndex = 0;
163 		timer.Reset();
164 
165 		int numberOfThreads = 16;
166 		for(int i = 0; i < numberOfThreads; i++) {
167 			std::thread *testThread = new std::thread(RunTest);
168 			testThreads.push_back(testThread);
169 		}
170 
171 		for(int i = 0; i < numberOfThreads; i++) {
172 			testThreads[i]->join();
173 			delete testThreads[i];
174 		}
175 
176 		if(!failedTests.empty()) {
177 			std::cout << std::endl << std::endl;
178 			std::cout << "------------" << std::endl;
179 			std::cout << "Failed tests" << std::endl;
180 			std::cout << "------------" << std::endl;
181 			for(int i = 0; i < (int)failedTests.size(); i++) {
182 				std::cout << failedTests[i] << " (" << std::to_string(failedTestErrorCode[i]) << ")" << std::endl;
183 			}
184 			std::cout << std::endl << std::to_string(failedTests.size()) << " tests failed." << std::endl;
185 		} else {
186 			std::cout << std::endl << std::endl << "All tests passed.";
187 		}
188 		std::cout << std::endl << std::endl << "Elapsed time: " << (timer.GetElapsedMS() / 1000) << " seconds";
189 
190 		std::getchar();
191 	} else if(argc == 3) {
192 		char* testFilename = argv[2];
193 		InitDll();
194 		SetFlags(0x8000000000000000); //EmulationFlags::ConsoleMode
195 		InitializeEmu(mesenFolder.c_str(), nullptr, nullptr, false, false, false);
196 		RegisterNotificationCallback(0, (NotificationListenerCallback)OnNotificationReceived);
197 		SetControllerType(0, ControllerType::StandardController);
198 		SetControllerType(1, ControllerType::StandardController);
199 
200 		int result = 0;
201 		if(strcmp(argv[1], "/testrom") == 0) {
202 			result = RunRecordedTest(testFilename);
203 		} else {
204 			result = RunAutomaticTest(testFilename);
205 		}
206 
207 		if(runThread != nullptr) {
208 			runThread->join();
209 			delete runThread;
210 		}
211 		return result;
212 	}
213 	return 0;
214 }
215 
216