Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util-tests/cue_parser_tests.cpp
4802 views
1
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "util/cue_parser.h"
5
6
#include "common/error.h"
7
8
#include <gtest/gtest.h>
9
10
// Test basic valid CUE sheet
11
TEST(CueParser, BasicValidCue)
12
{
13
const std::string cue = "FILE \"game.bin\" BINARY\n"
14
"TRACK 01 MODE2/2352\n"
15
"INDEX 01 00:00:00\n";
16
17
CueParser::File cue_file;
18
Error error;
19
20
ASSERT_TRUE(cue_file.Parse(cue, &error));
21
22
const CueParser::Track* track = cue_file.GetTrack(1);
23
ASSERT_NE(track, nullptr);
24
EXPECT_EQ(track->number, 1);
25
EXPECT_EQ(track->mode, CueParser::TrackMode::Mode2Raw);
26
EXPECT_EQ(track->file, "game.bin");
27
EXPECT_EQ(track->file_format, CueParser::FileFormat::Binary);
28
29
const CueParser::MSF* index1 = track->GetIndex(1);
30
ASSERT_NE(index1, nullptr);
31
EXPECT_EQ(index1->minute, 0);
32
EXPECT_EQ(index1->second, 0);
33
EXPECT_EQ(index1->frame, 0);
34
}
35
36
// Test multiple tracks
37
TEST(CueParser, MultipleTracksCue)
38
{
39
const std::string cue = "FILE \"game.bin\" BINARY\n"
40
"TRACK 01 MODE1/2352\n"
41
"INDEX 01 00:00:00\n"
42
"TRACK 02 AUDIO\n"
43
"INDEX 00 02:30:65\n"
44
"INDEX 01 02:31:45\n"
45
"TRACK 03 MODE2/2336\n"
46
"INDEX 01 05:45:20\n";
47
48
CueParser::File cue_file;
49
Error error;
50
51
ASSERT_TRUE(cue_file.Parse(cue, &error));
52
53
// Test track 1
54
const CueParser::Track* track1 = cue_file.GetTrack(1);
55
ASSERT_NE(track1, nullptr);
56
EXPECT_EQ(track1->number, 1);
57
EXPECT_EQ(track1->mode, CueParser::TrackMode::Mode1Raw);
58
59
// Test track 2
60
const CueParser::Track* track2 = cue_file.GetTrack(2);
61
ASSERT_NE(track2, nullptr);
62
EXPECT_EQ(track2->number, 2);
63
EXPECT_EQ(track2->mode, CueParser::TrackMode::Audio);
64
65
// Check track 2 indices
66
const CueParser::MSF* index0 = track2->GetIndex(0);
67
ASSERT_NE(index0, nullptr);
68
EXPECT_EQ(index0->minute, 2);
69
EXPECT_EQ(index0->second, 30);
70
EXPECT_EQ(index0->frame, 65);
71
72
const CueParser::MSF* index1 = track2->GetIndex(1);
73
ASSERT_NE(index1, nullptr);
74
EXPECT_EQ(index1->minute, 2);
75
EXPECT_EQ(index1->second, 31);
76
EXPECT_EQ(index1->frame, 45);
77
78
// Test track 3
79
const CueParser::Track* track3 = cue_file.GetTrack(3);
80
ASSERT_NE(track3, nullptr);
81
EXPECT_EQ(track3->number, 3);
82
EXPECT_EQ(track3->mode, CueParser::TrackMode::Mode2);
83
}
84
85
// Test different track modes
86
TEST(CueParser, DifferentTrackModes)
87
{
88
const std::string cue = "FILE \"audio.wav\" WAVE\n"
89
"TRACK 01 AUDIO\n"
90
"INDEX 01 00:00:00\n"
91
"FILE \"data.bin\" BINARY\n"
92
"TRACK 02 MODE1/2048\n"
93
"INDEX 01 00:00:00\n"
94
"TRACK 03 MODE1/2352\n"
95
"INDEX 01 15:34:50\n"
96
"TRACK 04 MODE2/2048\n"
97
"INDEX 01 30:10:00\n"
98
"TRACK 05 MODE2/2352\n"
99
"INDEX 01 45:00:00\n";
100
101
CueParser::File cue_file;
102
Error error;
103
104
ASSERT_TRUE(cue_file.Parse(cue, &error));
105
106
// Verify track modes
107
const CueParser::Track* track1 = cue_file.GetTrack(1);
108
ASSERT_NE(track1, nullptr);
109
EXPECT_EQ(track1->mode, CueParser::TrackMode::Audio);
110
EXPECT_EQ(track1->file_format, CueParser::FileFormat::Wave);
111
112
const CueParser::Track* track2 = cue_file.GetTrack(2);
113
ASSERT_NE(track2, nullptr);
114
EXPECT_EQ(track2->mode, CueParser::TrackMode::Mode1);
115
EXPECT_EQ(track2->file_format, CueParser::FileFormat::Binary);
116
117
const CueParser::Track* track3 = cue_file.GetTrack(3);
118
ASSERT_NE(track3, nullptr);
119
EXPECT_EQ(track3->mode, CueParser::TrackMode::Mode1Raw);
120
121
const CueParser::Track* track4 = cue_file.GetTrack(4);
122
ASSERT_NE(track4, nullptr);
123
EXPECT_EQ(track4->mode, CueParser::TrackMode::Mode2Form1);
124
125
const CueParser::Track* track5 = cue_file.GetTrack(5);
126
ASSERT_NE(track5, nullptr);
127
EXPECT_EQ(track5->mode, CueParser::TrackMode::Mode2Raw);
128
}
129
130
// Test track flags
131
TEST(CueParser, TrackFlags)
132
{
133
const std::string cue = "FILE \"game.bin\" BINARY\n"
134
"TRACK 01 AUDIO\n"
135
"FLAGS PRE DCP 4CH\n"
136
"INDEX 01 00:00:00\n"
137
"TRACK 02 AUDIO\n"
138
"FLAGS SCMS\n"
139
"INDEX 01 03:00:00\n";
140
141
CueParser::File cue_file;
142
Error error;
143
144
ASSERT_TRUE(cue_file.Parse(cue, &error));
145
146
const CueParser::Track* track1 = cue_file.GetTrack(1);
147
ASSERT_NE(track1, nullptr);
148
EXPECT_TRUE(track1->HasFlag(CueParser::TrackFlag::PreEmphasis));
149
EXPECT_TRUE(track1->HasFlag(CueParser::TrackFlag::CopyPermitted));
150
EXPECT_TRUE(track1->HasFlag(CueParser::TrackFlag::FourChannelAudio));
151
EXPECT_FALSE(track1->HasFlag(CueParser::TrackFlag::SerialCopyManagement));
152
153
const CueParser::Track* track2 = cue_file.GetTrack(2);
154
ASSERT_NE(track2, nullptr);
155
EXPECT_FALSE(track2->HasFlag(CueParser::TrackFlag::PreEmphasis));
156
EXPECT_FALSE(track2->HasFlag(CueParser::TrackFlag::CopyPermitted));
157
EXPECT_FALSE(track2->HasFlag(CueParser::TrackFlag::FourChannelAudio));
158
EXPECT_TRUE(track2->HasFlag(CueParser::TrackFlag::SerialCopyManagement));
159
}
160
161
// Test PREGAP handling
162
TEST(CueParser, PregapHandling)
163
{
164
const std::string cue = "FILE \"game.bin\" BINARY\n"
165
"TRACK 01 AUDIO\n"
166
"INDEX 01 00:00:00\n"
167
"TRACK 02 AUDIO\n"
168
"PREGAP 00:02:00\n"
169
"INDEX 01 03:00:00\n";
170
171
CueParser::File cue_file;
172
Error error;
173
174
ASSERT_TRUE(cue_file.Parse(cue, &error));
175
176
const CueParser::Track* track1 = cue_file.GetTrack(1);
177
ASSERT_NE(track1, nullptr);
178
EXPECT_FALSE(track1->zero_pregap.has_value());
179
180
const CueParser::Track* track2 = cue_file.GetTrack(2);
181
ASSERT_NE(track2, nullptr);
182
ASSERT_TRUE(track2->zero_pregap.has_value());
183
EXPECT_EQ(track2->zero_pregap->minute, 0);
184
EXPECT_EQ(track2->zero_pregap->second, 2);
185
EXPECT_EQ(track2->zero_pregap->frame, 0);
186
}
187
188
// Test track lengths are correctly calculated
189
TEST(CueParser, TrackLengthCalculation)
190
{
191
const std::string cue = "FILE \"game.bin\" BINARY\n"
192
"TRACK 01 AUDIO\n"
193
"INDEX 01 00:00:00\n"
194
"TRACK 02 AUDIO\n"
195
"INDEX 00 02:30:00\n"
196
"INDEX 01 02:32:00\n"
197
"TRACK 03 AUDIO\n"
198
"INDEX 01 05:45:20\n";
199
200
CueParser::File cue_file;
201
Error error;
202
203
ASSERT_TRUE(cue_file.Parse(cue, &error));
204
205
const CueParser::Track* track1 = cue_file.GetTrack(1);
206
ASSERT_NE(track1, nullptr);
207
ASSERT_TRUE(track1->length.has_value());
208
// Track 1 length should be calculated to end at track 2's index 0
209
EXPECT_EQ(track1->length->minute, 2);
210
EXPECT_EQ(track1->length->second, 30);
211
EXPECT_EQ(track1->length->frame, 0);
212
213
const CueParser::Track* track2 = cue_file.GetTrack(2);
214
ASSERT_NE(track2, nullptr);
215
ASSERT_TRUE(track2->length.has_value());
216
// Track 2 length should be calculated to end at track 3's index 1
217
EXPECT_EQ(track2->length->minute, 3);
218
EXPECT_EQ(track2->length->second, 13);
219
EXPECT_EQ(track2->length->frame, 20);
220
}
221
222
// Test missing FILE command
223
TEST(CueParser, MissingFileCommand)
224
{
225
const std::string cue = "TRACK 01 AUDIO\n"
226
"INDEX 01 00:00:00\n";
227
228
CueParser::File cue_file;
229
Error error;
230
ASSERT_FALSE(cue_file.Parse(cue, &error));
231
}
232
233
// Test invalid track number
234
TEST(CueParser, InvalidTrackNumber)
235
{
236
const std::string cue = "FILE \"game.bin\" BINARY\n"
237
"TRACK 100 AUDIO\n"
238
"INDEX 01 00:00:00\n";
239
240
CueParser::File cue_file;
241
Error error;
242
ASSERT_FALSE(cue_file.Parse(cue, &error));
243
}
244
245
// Test missing index 1
246
TEST(CueParser, MissingIndex1)
247
{
248
const std::string cue = "FILE \"game.bin\" BINARY\n"
249
"TRACK 01 AUDIO\n"
250
"INDEX 00 00:00:00\n";
251
252
CueParser::File cue_file;
253
Error error;
254
ASSERT_FALSE(cue_file.Parse(cue, &error));
255
}
256
257
// Test invalid index number
258
TEST(CueParser, InvalidIndexNumber)
259
{
260
const std::string cue = "FILE \"game.bin\" BINARY\n"
261
"TRACK 01 AUDIO\n"
262
"INDEX 100 00:00:00\n";
263
264
CueParser::File cue_file;
265
Error error;
266
ASSERT_FALSE(cue_file.Parse(cue, &error));
267
}
268
269
// Test invalid MSF format
270
TEST(CueParser, InvalidMSFFormat)
271
{
272
const std::string cue = "FILE \"game.bin\" BINARY\n"
273
"TRACK 01 AUDIO\n"
274
"INDEX 01 00:99:00\n";
275
276
CueParser::File cue_file;
277
Error error;
278
ASSERT_FALSE(cue_file.Parse(cue, &error));
279
}
280
281
// Test duplicate index
282
TEST(CueParser, DuplicateIndex)
283
{
284
const std::string cue = "FILE \"game.bin\" BINARY\n"
285
"TRACK 01 AUDIO\n"
286
"INDEX 01 00:00:00\n"
287
"INDEX 01 00:01:00\n";
288
289
CueParser::File cue_file;
290
Error error;
291
ASSERT_FALSE(cue_file.Parse(cue, &error));
292
}
293
294
// Test reverse index order
295
TEST(CueParser, ReverseIndexOrder)
296
{
297
const std::string cue = "FILE \"game.bin\" BINARY\n"
298
"TRACK 01 AUDIO\n"
299
"INDEX 02 00:01:00\n"
300
"INDEX 01 00:02:00\n";
301
302
CueParser::File cue_file;
303
Error error;
304
ASSERT_FALSE(cue_file.Parse(cue, &error));
305
}
306
307
// Test handling of comments and whitespace
308
TEST(CueParser, CommentsAndWhitespace)
309
{
310
const std::string cue = "REM This is a comment\n"
311
" FILE \"game.bin\" BINARY \n"
312
"\n"
313
"REM Another comment\n"
314
" TRACK 01 MODE2/2352 \n"
315
" INDEX 01 00:00:00 \n";
316
317
CueParser::File cue_file;
318
Error error;
319
320
ASSERT_TRUE(cue_file.Parse(cue, &error));
321
322
const CueParser::Track* track = cue_file.GetTrack(1);
323
ASSERT_NE(track, nullptr);
324
EXPECT_EQ(track->mode, CueParser::TrackMode::Mode2Raw);
325
}
326
327
// Test handling of multiple files
328
TEST(CueParser, MultipleFiles)
329
{
330
const std::string cue = "FILE \"track1.bin\" BINARY\n"
331
"TRACK 01 MODE1/2352\n"
332
"INDEX 01 00:00:00\n"
333
"FILE \"track2.wav\" WAVE\n"
334
"TRACK 02 AUDIO\n"
335
"INDEX 01 00:00:00\n";
336
337
CueParser::File cue_file;
338
Error error;
339
340
ASSERT_TRUE(cue_file.Parse(cue, &error));
341
342
const CueParser::Track* track1 = cue_file.GetTrack(1);
343
ASSERT_NE(track1, nullptr);
344
EXPECT_EQ(track1->file, "track1.bin");
345
EXPECT_EQ(track1->file_format, CueParser::FileFormat::Binary);
346
347
const CueParser::Track* track2 = cue_file.GetTrack(2);
348
ASSERT_NE(track2, nullptr);
349
EXPECT_EQ(track2->file, "track2.wav");
350
EXPECT_EQ(track2->file_format, CueParser::FileFormat::Wave);
351
}
352
353
// Test ignoring unsupported metadata
354
TEST(CueParser, IgnoreUnsupportedMetadata)
355
{
356
const std::string cue = "CATALOG 1234567890123\n"
357
"TITLE \"Game Title\"\n"
358
"PERFORMER \"Developer\"\n"
359
"FILE \"game.bin\" BINARY\n"
360
"TRACK 01 MODE2/2352\n"
361
"TITLE \"Track Title\"\n"
362
"INDEX 01 00:00:00\n";
363
364
CueParser::File cue_file;
365
Error error;
366
367
ASSERT_TRUE(cue_file.Parse(cue, &error));
368
369
const CueParser::Track* track = cue_file.GetTrack(1);
370
ASSERT_NE(track, nullptr);
371
EXPECT_EQ(track->number, 1);
372
}
373
374