testgamedemo.cpp
1 //------------------------------------------------------------------------------
2 // testgamedemo.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2024 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "gamedemo.h"
24 #include "testgamedemo.h"
25 
27 #include "tests/asserts.h"
28 
29 #include <QDate>
30 #include <QDateTime>
31 #include <QTime>
32 #include <QTimeZone>
33 #include <QString>
34 
35 static GameDemo mkdemometa(QString author = "Player", QString game = "Game",
36  QDateTime time = QDateTime::fromMSecsSinceEpoch(0, Qt::UTC),
37  QString demopath = "demo.lmp",
38  QString iwad = "freedoom.wad", QList<PWad> wads = {})
39 {
40  GameDemo demo;
41  demo.demopath = demopath;
42  demo.author = author;
43  demo.game = game;
44  demo.time = time;
45  demo.iwad = iwad;
46  demo.wads = wads;
47  return demo;
48 }
49 
50 bool TestGameDemoExportedName::executeTest()
51 {
52  auto withAuthor = [](QString name)
53  {
54  return QString("%1_Game_1970-01-01T000000Z.lmp").arg(name);
55  };
56 
57  auto withGame = [](QString name)
58  {
59  return QString("Player_%1_1970-01-01T000000Z.lmp").arg(name);
60  };
61 
62  // The plainest check.
63  // Time is saved in the basic format without the ':' to make the filename more /kosher/.
64  T_ASSERT_EQUAL("Player_Game_1970-01-01T000000Z.lmp",
65  mkdemometa("Player", "Game").exportedName());
66 
67  // Demo recorded during the period of Roman Empire should be prefixed with leading zeros.
68  T_ASSERT_EQUAL("Player_Game_0055-06-11T133700Z.lmp",
69  mkdemometa("Player", "Game",
70  QDateTime(QDate(55, 6, 11), QTime(13, 37), Qt::UTC)).exportedName());
71 
72  // To avoid making filenames more complex, all dates should be saved in UTC.
73  T_ASSERT_EQUAL("Player_Doom_2024-02-01T113721Z.lmp",
74  mkdemometa("Player", "Doom",
75  QDateTime(QDate(2024, 2, 1), QTime(13, 37, 21), Qt::OffsetFromUTC, 2 * 60 * 60))
76  .exportedName());
77 
78  // Empty author and game still produce some kind of path.
79  T_ASSERT_EQUAL("unknownplayer_unknowngame_1970-01-01T000000Z.lmp",
80  mkdemometa("", "").exportedName());
81 
82  // Invalid datetime, empty author and empty game also produce some kind of path.
83  T_ASSERT_EQUAL("unknownplayer_unknowngame_unknowntime.lmp",
84  mkdemometa("", "", QDateTime()).exportedName());
85 
86  // Author name not /kosher/ for filepaths is mangled.
87  // Allowed characters: [A-Za-z0-9]_-
88  T_ASSERT_EQUAL(withAuthor(""), mkdemometa("/").exportedName());
89  T_ASSERT_EQUAL(withAuthor(""), mkdemometa("\\").exportedName());
90  T_ASSERT_EQUAL(withAuthor(""), mkdemometa(".").exportedName());
91  T_ASSERT_EQUAL(withAuthor(""), mkdemometa("/./...").exportedName());
92  T_ASSERT_EQUAL(withAuthor("mynameisleethx"), mkdemometa("$my:name+is!leet#h.x").exportedName());
93  T_ASSERT_EQUAL(withAuthor("CLANValid-1337"), mkdemometa("_CLAN_Valid-1337").exportedName());
94  T_ASSERT_EQUAL(withAuthor("CanIhazspace"), mkdemometa("Can I haz space").exportedName());
95 
96  // Author name longer than some limit of characters is trimmed.
97  QString longname = "IconOfDoomseeker";
98  while (longname.size() < GameDemo::MAX_AUTHOR_FILENAME_LEN + 1)
99  longname += longname; // this is exponential, but w/e
100  QString shortenedName = longname.left(GameDemo::MAX_AUTHOR_FILENAME_LEN);
101  T_ASSERT_EQUAL(withAuthor(shortenedName), mkdemometa(longname).exportedName());
102 
103  // Games in normal operation don't have non-/kosher/ characters (except maybe space),
104  // but let's test them anyway.
105  T_ASSERT_EQUAL(withGame(""), mkdemometa("Player", "/").exportedName());
106  T_ASSERT_EQUAL(withGame(""), mkdemometa("Player", "\\").exportedName());
107  T_ASSERT_EQUAL(withGame(""), mkdemometa("Player", ".").exportedName());
108  T_ASSERT_EQUAL(withGame(""), mkdemometa("Player", "/./...").exportedName());
109  T_ASSERT_EQUAL(withGame(""), mkdemometa("Player", " ").exportedName());
110  T_ASSERT_EQUAL(withGame("mynameisleethx"), mkdemometa("Player", "$my:name+is!leet#h.x").exportedName());
111  T_ASSERT_EQUAL(withGame("CLANValid-1337"), mkdemometa("Player", "_CLAN_Valid-1337").exportedName());
112  T_ASSERT_EQUAL(withGame("CanIhazspace"), mkdemometa("Player", "Can I haz space").exportedName());
113 
114  return true;
115 }
116 
117 bool TestGameDemoImprintPath::executeTest()
118 {
119  auto imprint = [](QString path)
120  {
121  GameDemo demo;
122  demo.imprintPath(path);
123  return demo;
124  };
125 
126  { // V1, plain
127  GameDemo demo = imprint("Emag_15.12.1997_15.16.17.lmp");
128  T_ASSERT_EQUAL("Emag", demo.game);
129  T_ASSERT_DATETIME_EQUAL(QDateTime(QDate(1997, 12, 15), QTime(15, 16, 17)), demo.time);
130  T_ASSERT_EQUAL("", demo.author);
131  T_ASSERT_EQUAL("", demo.iwad);
132  T_ASSERT_ISEMPTY(demo.wads);
133  }
134 
135  { // V1, with wads
136  GameDemo demo = imprint("Emag_15.12.1997_15.16.17_freedoom.wad_av.wad_zvox.wad.lmp");
137  T_ASSERT_EQUAL("Emag", demo.game);
138  T_ASSERT_DATETIME_EQUAL(QDateTime(QDate(1997, 12, 15), QTime(15, 16, 17)), demo.time);
139  T_ASSERT_EQUAL("", demo.author);
140  T_ASSERT_EQUAL("freedoom.wad", demo.iwad);
141  T_ASSERT_SIZE(2, demo.wads);
142  T_ASSERT_EQUAL("av.wad", demo.wads[0].name());
143  T_ASSERT_FALSE(demo.wads[0].isOptional());
144  T_ASSERT_EQUAL("zvox.wad", demo.wads[1].name());
145  T_ASSERT_FALSE(demo.wads[1].isOptional());
146  }
147 
148  { // V1, wad with underscore
149  GameDemo demo = imprint("Emag_15.12.1997_15.16.17_free__doom.wad.lmp");
150  T_ASSERT_EQUAL("free_doom.wad", demo.iwad);
151  }
152 
153  { // V2 (the year part is deliberately mismatching)
154  GameDemo demo = imprint("Lepray/2023/Emag_1997-12-15T151617Z.lmp");
155  T_ASSERT_EQUAL("Emag", demo.game);
156  T_ASSERT_DATETIME_EQUAL(QDateTime(QDate(1997, 12, 15), QTime(15, 16, 17), Qt::UTC), demo.time);
157 
158  T_ASSERT_EQUAL("Lepray", demo.author);
159  T_ASSERT_EQUAL("", demo.iwad);
160  T_ASSERT_ISEMPTY(demo.wads);
161  }
162 
163  { // Exported
164  GameDemo demo = imprint("Lepray_Emag_1997-12-15T151617Z.lmp");
165  T_ASSERT_EQUAL("Emag", demo.game);
166  T_ASSERT_DATETIME_EQUAL(QDateTime(QDate(1997, 12, 15), QTime(15, 16, 17), Qt::UTC), demo.time);
167 
168  T_ASSERT_EQUAL("Lepray", demo.author);
169  T_ASSERT_EQUAL("", demo.iwad);
170  T_ASSERT_ISEMPTY(demo.wads);
171  }
172 
173  // Now try some mismatching names.
174 
175  { // no player or game info
176  GameDemo demo = imprint("_1997-12-15T151617Z.lmp");
177  T_ASSERT_EQUAL("", demo.game);
178  T_ASSERT_DATETIME_EQUAL(QDateTime(QDate(1997, 12, 15), QTime(15, 16, 17), Qt::UTC), demo.time);
179 
180  T_ASSERT_EQUAL("", demo.author);
181  T_ASSERT_EQUAL("", demo.iwad);
182  T_ASSERT_ISEMPTY(demo.wads);
183  }
184 
185  { // It's V2, but no player name
186  GameDemo demo = imprint("1997/Emag_1997-12-15T151617Z.lmp");
187  T_ASSERT_EQUAL("Emag", demo.game);
188  T_ASSERT_DATETIME_EQUAL(QDateTime(QDate(1997, 12, 15), QTime(15, 16, 17), Qt::UTC), demo.time);
189 
190  T_ASSERT_EQUAL("", demo.author);
191  T_ASSERT_EQUAL("", demo.iwad);
192  T_ASSERT_ISEMPTY(demo.wads);
193  }
194 
195  { // It's V2, but no player name and no year
196  GameDemo demo = imprint("Emga_1997-12-15T151617Z.lmp");
197  T_ASSERT_EQUAL("Emga", demo.game);
198  T_ASSERT_DATETIME_EQUAL(QDateTime(QDate(1997, 12, 15), QTime(15, 16, 17), Qt::UTC), demo.time);
199 
200  T_ASSERT_EQUAL("", demo.author);
201  T_ASSERT_EQUAL("", demo.iwad);
202  T_ASSERT_ISEMPTY(demo.wads);
203  }
204 
205 
206  { // Not a supported format
207  GameDemo demo = imprint("watermelon.lmp");
208  T_ASSERT_EQUAL("", demo.game);
209  T_ASSERT_FALSE(demo.time.isValid());
210  T_ASSERT_EQUAL("", demo.author);
211  T_ASSERT_EQUAL("", demo.iwad);
212  T_ASSERT_ISEMPTY(demo.wads);
213  }
214 
215  return true;
216 }
217 
218 bool TestGameDemoManagedName::executeTest()
219 {
220  auto withAuthor = [](QString name)
221  {
222  return QString("%1/1970/Game_1970-01-01T000000Z.lmp").arg(name);
223  };
224 
225  auto withGame = [](QString name)
226  {
227  return QString("Player/1970/%1_1970-01-01T000000Z.lmp").arg(name);
228  };
229 
230  // The plainest check.
231  // Time is saved in the basic format without the ':' to make the filename more /kosher/.
232  T_ASSERT_EQUAL("Player/1970/Game_1970-01-01T000000Z.lmp",
233  mkdemometa("Player", "Game").managedName());
234 
235  // Demo recorded during the period of Roman Empire should be prefixed with leading zeros.
236  T_ASSERT_EQUAL("Player/0055/Game_0055-06-11T133700Z.lmp",
237  mkdemometa("Player", "Game",
238  QDateTime(QDate(55, 6, 11), QTime(13, 37), Qt::UTC)).managedName());
239 
240  // To avoid making filenames more complex, all dates should be saved in UTC.
241  T_ASSERT_EQUAL("Player/2024/Doom_2024-02-01T113721Z.lmp",
242  mkdemometa("Player", "Doom",
243  QDateTime(QDate(2024, 2, 1), QTime(13, 37, 21), Qt::OffsetFromUTC, 2 * 60 * 60))
244  .managedName());
245 
246  // Empty author and game still produce some kind of path.
247  T_ASSERT_EQUAL("_/1970/_1970-01-01T000000Z.lmp",
248  mkdemometa("", "").managedName());
249 
250  // Invalid datetime, empty author and empty game also produce some kind of path.
251  T_ASSERT_EQUAL("_/_/_unknown.lmp",
252  mkdemometa("", "", QDateTime()).managedName());
253 
254  // Author name not /kosher/ for filepaths is mangled.
255  // Allowed characters: [A-Za-z0-9]_-
256  T_ASSERT_EQUAL(withAuthor("_"), mkdemometa("/").managedName());
257  T_ASSERT_EQUAL(withAuthor("_"), mkdemometa("\\").managedName());
258  T_ASSERT_EQUAL(withAuthor("_"), mkdemometa(".").managedName());
259  T_ASSERT_EQUAL(withAuthor("_"), mkdemometa("/./...").managedName());
260  T_ASSERT_EQUAL(withAuthor("_"), mkdemometa(" ").managedName());
261  T_ASSERT_EQUAL(withAuthor("mynameisleethx"), mkdemometa("$my:name+is!leet#h.x").managedName());
262  T_ASSERT_EQUAL(withAuthor("CLANValid-1337"), mkdemometa("_CLAN_Valid-1337").managedName());
263  T_ASSERT_EQUAL(withAuthor("CanIhazspace"), mkdemometa("Can I haz space").managedName());
264 
265  // Author name longer than some limit of characters is trimmed.
266  QString longname = "IconOfDoomseeker";
267  while (longname.size() < GameDemo::MAX_AUTHOR_FILENAME_LEN + 1)
268  longname += longname; // this is exponential, but w/e
269  QString shortenedName = longname.left(GameDemo::MAX_AUTHOR_FILENAME_LEN);
270  T_ASSERT_EQUAL(withAuthor(shortenedName), mkdemometa(longname).managedName());
271 
272  // Games in normal operation don't have non-/kosher/ characters (except maybe space),
273  // but let's test them anyway.
274  T_ASSERT_EQUAL(withGame(""), mkdemometa("Player", "/").managedName());
275  T_ASSERT_EQUAL(withGame(""), mkdemometa("Player", "\\").managedName());
276  T_ASSERT_EQUAL(withGame(""), mkdemometa("Player", ".").managedName());
277  T_ASSERT_EQUAL(withGame(""), mkdemometa("Player", "/./...").managedName());
278  T_ASSERT_EQUAL(withGame(""), mkdemometa("Player", " ").managedName());
279  T_ASSERT_EQUAL(withGame("mynameisleethx"), mkdemometa("Player", "$my:name+is!leet#h.x").managedName());
280  T_ASSERT_EQUAL(withGame("CLANValid-1337"), mkdemometa("Player", "_CLAN_Valid-1337").managedName());
281 
282  return true;
283 }