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