1 #include <boost/test/unit_test.hpp>
2
3 #include "ClientAppFixture.h"
4 #include "Empire/Empire.h"
5 #include "util/Directories.h"
6 #include "util/Process.h"
7 #include "util/SitRepEntry.h"
8
9 namespace {
ServerClientExe()10 std::string ServerClientExe() {
11 #ifdef FREEORION_WIN32
12 return PathToString(GetBinDir() / "freeoriond.exe");
13 #else
14 return (GetBinDir() / "freeoriond").string();
15 #endif
16 }
17
18 constexpr static int MAX_WAITING_SEC = 120;
19 }
20
21 #ifdef FREEORION_MACOSX
22 #include <stdlib.h>
23 #endif
24
BOOST_FIXTURE_TEST_SUITE(SmokeTestHostless,ClientAppFixture)25 BOOST_FIXTURE_TEST_SUITE(SmokeTestHostless, ClientAppFixture)
26
27 /**
28 * - Do start a server with hostless mode if `FO_TEST_HOSTLESS_LAUNCH_SERVER` was set with save
29 * enabled if `FO_TEST_HOSTLESS_SAVE` was set.
30 * - Do connect to lobby to localhost server as a Player.
31 * - Expect successfully connection to localhost server.
32 * - Do add `FO_TEST_HOSTLESS_AIS` AIs to lobby (by default 2).
33 * - Expect AIs will be added successfully.
34 * - Do make player ready.
35 * - Expect game started.
36 * - Do make empty turns until turn number reach `FO_TEST_HOSTLESS_TURNS` or mock player will be
37 * eliminated or someone will win.
38 * - Expect turns're processing without error.
39 * - Do disconnect from server.
40 * - Do reconnect to server until number of played games reach `FO_TEST_HOSTLESS_GAMES`.
41 * - Expect got to lobby.
42 * - Expect number of AIs was preserved.
43 * - Do repeat game steps.
44 * - Expect all games processed correctly.
45 * - Do shut down server.
46 * - Expect that serve and AIs were shut down.
47 */
48
49 BOOST_AUTO_TEST_CASE(hostless_server) {
50 unsigned int num_AIs = 2;
51 unsigned int num_games = 3;
52 int num_turns = 3;
53 bool save_game = true;
54 bool launch_server = true;
55
56 const char *env_num_AIs = std::getenv("FO_TEST_HOSTLESS_AIS");
57 if (env_num_AIs) {
58 try {
59 num_AIs = boost::lexical_cast<unsigned int>(env_num_AIs);
60 } catch (...) {
61 // ignore
62 }
63 }
64
65 const char *env_num_turns = std::getenv("FO_TEST_HOSTLESS_TURNS");
66 if (env_num_turns) {
67 try {
68 num_turns = boost::lexical_cast<int>(env_num_turns);
69 } catch (...) {
70 // ignore
71 }
72 }
73
74 const char *env_save_game = std::getenv("FO_TEST_HOSTLESS_SAVE");
75 if (env_save_game) {
76 try {
77 save_game = boost::lexical_cast<int>(env_save_game) != 0;
78 } catch (...) {
79 // ignore
80 }
81 }
82
83 const char *env_launch_server = std::getenv("FO_TEST_HOSTLESS_LAUNCH_SERVER");
84 if (env_launch_server) {
85 try {
86 launch_server = boost::lexical_cast<int>(env_launch_server) != 0;
87 } catch (...) {
88 // ignore
89 }
90 }
91
92 const char *env_games = std::getenv("FO_TEST_HOSTLESS_GAMES");
93 if (env_games) {
94 try {
95 num_games = boost::lexical_cast<unsigned int>(env_games);
96 } catch (...) {
97 // ignore
98 }
99 }
100
101 boost::optional<Process> server;
102 if (launch_server) {
103 BOOST_REQUIRE(!PingLocalHostServer());
104
105 std::string SERVER_CLIENT_EXE = ServerClientExe();
106
107 BOOST_TEST_MESSAGE(SERVER_CLIENT_EXE);
108
109 #ifdef FREEORION_MACOSX
110 // On OSX set environment variable DYLD_LIBRARY_PATH to python framework folder
111 // bundled with app, so the dynamic linker uses the bundled python library.
112 // Otherwise the dynamic linker will look for a correct python lib in system
113 // paths, and if it can't find it, throw an error and terminate!
114 // Setting environment variable here, spawned child processes will inherit it.
115 setenv("DYLD_LIBRARY_PATH", GetPythonHome().string().c_str(), 1);
116 #endif
117
118 std::vector<std::string> args;
119 args.push_back("\"" + SERVER_CLIENT_EXE + "\"");
120 args.push_back("--hostless");
121 args.push_back("--save.auto.hostless.enabled");
122 args.push_back(save_game ? "1" : "0");
123 args.push_back("--setup.ai.player.count");
124 args.push_back("0");
125 args.push_back("--testing");
126
127 #ifdef FREEORION_LINUX
128 // Dirty hack to output log to console.
129 args.push_back("--log-file");
130 args.push_back("/proc/self/fd/1");
131 #endif
132
133 server = Process(SERVER_CLIENT_EXE, args);
134
135 BOOST_TEST_MESSAGE("Server started.");
136 }
137
138 for (unsigned int g = 0; g < num_games; ++g) {
139 m_game_started = false;
140 BOOST_TEST_MESSAGE("Game " << g << ". Connecting to server...");
141
142 BOOST_REQUIRE(ConnectToServer("localhost"));
143
144 JoinGame();
145 boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time();
146 start_time = boost::posix_time::microsec_clock::local_time();
147 while (!m_lobby_updated) {
148 BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC));
149 }
150 BOOST_TEST_MESSAGE("Entered to lobby");
151
152 if (g == 0) {
153 // if first game lobby should be empty
154 BOOST_REQUIRE_EQUAL(GetLobbyAICount(), 0);
155
156 // fill lobby with AIs
157 for (unsigned int ai_i = 1; ai_i <= num_AIs; ++ai_i) {
158 PlayerSetupData ai_plr;
159 ai_plr.m_client_type = Networking::CLIENT_TYPE_AI_PLAYER;
160 m_lobby_data.m_players.push_back({Networking::INVALID_PLAYER_ID, ai_plr});
161 // publish changes
162 UpdateLobby();
163 start_time = boost::posix_time::microsec_clock::local_time();
164 while (!m_lobby_updated) {
165 BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC));
166 }
167 }
168 }
169 // after filling first game there should be corrent number of AIs
170 // and other game should retain number of AIs from previous game
171 BOOST_REQUIRE_EQUAL(GetLobbyAICount(), num_AIs);
172
173 // get ready
174 for (auto& plr : m_lobby_data.m_players) {
175 if (plr.first == PlayerID())
176 plr.second.m_player_ready = true;
177 }
178 UpdateLobby();
179
180 start_time = boost::posix_time::microsec_clock::local_time();
181 while (!m_game_started) {
182 BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC));
183 }
184
185 BOOST_REQUIRE_EQUAL(m_ai_empires.size(), num_AIs);
186
187 start_time = boost::posix_time::microsec_clock::local_time();
188 while (! m_ai_waiting.empty()) {
189 BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC));
190 }
191
192 SaveGameUIData ui_data;
193
194 while (m_current_turn <= num_turns) {
195 SendPartialOrders();
196
197 StartTurn(ui_data);
198
199 m_turn_done = false;
200 m_ai_waiting = m_ai_empires;
201
202 BOOST_TEST_MESSAGE("Turn done. Waiting server for update...");
203 start_time = boost::posix_time::microsec_clock::local_time();
204 while (!m_turn_done) {
205 BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC));
206 }
207
208 // output sitreps
209 const Empire* my_empire = m_empires.GetEmpire(m_empire_id);
210 BOOST_REQUIRE(my_empire != nullptr);
211 for (auto sitrep_it = my_empire->SitRepBegin(); sitrep_it != my_empire->SitRepEnd(); ++sitrep_it) {
212 if (sitrep_it->GetTurn() == m_current_turn) {
213 BOOST_TEST_MESSAGE("Sitrep: " << sitrep_it->Dump());
214 }
215 }
216
217 if (my_empire->Eliminated()) {
218 BOOST_TEST_MESSAGE("Test player empire was eliminated.");
219 break;
220 }
221 bool have_winner = false;
222 for (auto empire : m_empires) {
223 if (empire.second->Won()) {
224 have_winner = true;
225 break;
226 }
227 }
228 if (have_winner) {
229 BOOST_TEST_MESSAGE("Someone wins game.");
230 break;
231 }
232
233 BOOST_TEST_MESSAGE("Waiting AI for turns...");
234 start_time = boost::posix_time::microsec_clock::local_time();
235 while (! m_ai_waiting.empty()) {
236 BOOST_REQUIRE(ProcessMessages(start_time, MAX_WAITING_SEC));
237 }
238 }
239
240 DisconnectFromServer();
241
242 start_time = boost::posix_time::microsec_clock::local_time();
243 while (ProcessMessages(start_time, MAX_WAITING_SEC));
244 }
245
246 if (launch_server && server) {
247 BOOST_REQUIRE(server->Terminate());
248 }
249 }
250
251 BOOST_AUTO_TEST_SUITE_END()
252
253