Merge.
[libdcp.git] / test / verify_test.cc
1 /*
2     Copyright (C) 2018-2020 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34 #include "verify.h"
35 #include "util.h"
36 #include "compose.hpp"
37 #include <boost/test/unit_test.hpp>
38 #include <boost/foreach.hpp>
39 #include <boost/algorithm/string.hpp>
40 #include <cstdio>
41 #include <iostream>
42
43 using std::list;
44 using std::pair;
45 using std::string;
46 using std::vector;
47 using std::make_pair;
48 using boost::optional;
49
50 static list<pair<string, optional<boost::filesystem::path> > > stages;
51 static int next_verify_test_number = 1;
52
53 static void
54 stage (string s, optional<boost::filesystem::path> p)
55 {
56         stages.push_back (make_pair (s, p));
57 }
58
59 static void
60 progress (float)
61 {
62
63 }
64
65 static vector<boost::filesystem::path>
66 setup (int reference_number, int verify_test_number)
67 {
68         boost::filesystem::remove_all (dcp::String::compose("build/test/verify_test%1", verify_test_number));
69         boost::filesystem::create_directory (dcp::String::compose("build/test/verify_test%1", verify_test_number));
70         for (boost::filesystem::directory_iterator i(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number)); i != boost::filesystem::directory_iterator(); ++i) {
71                 boost::filesystem::copy_file (i->path(), dcp::String::compose("build/test/verify_test%1", verify_test_number) / i->path().filename());
72         }
73
74         vector<boost::filesystem::path> directories;
75         directories.push_back (dcp::String::compose("build/test/verify_test%1", verify_test_number));
76         return directories;
77
78 }
79
80 class Editor
81 {
82 public:
83         Editor (boost::filesystem::path path)
84                 : _path(path)
85         {
86                 _content = dcp::file_to_string (_path);
87         }
88
89         ~Editor ()
90         {
91                 FILE* f = fopen(_path.string().c_str(), "w");
92                 BOOST_REQUIRE (f);
93                 fwrite (_content.c_str(), _content.length(), 1, f);
94                 fclose (f);
95         }
96
97         void replace (string a, string b)
98         {
99                 boost::algorithm::replace_all (_content, a, b);
100         }
101
102 private:
103         boost::filesystem::path _path;
104         std::string _content;
105 };
106
107 static
108 void
109 dump_notes (list<dcp::VerificationNote> const & notes)
110 {
111         BOOST_FOREACH (dcp::VerificationNote i, notes) {
112                 std::cout << dcp::note_to_string(i) << "\n";
113         }
114 }
115
116 /* Check DCP as-is (should be OK) */
117 BOOST_AUTO_TEST_CASE (verify_test1)
118 {
119         stages.clear ();
120         vector<boost::filesystem::path> directories = setup (1, next_verify_test_number);
121         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
122
123         boost::filesystem::path const cpl_file = dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", next_verify_test_number);
124         boost::filesystem::path const pkl_file = dcp::String::compose("build/test/verify_test1/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml", next_verify_test_number);
125         boost::filesystem::path const assetmap_file = dcp::String::compose("build/test/verify_test1/ASSETMAP.xml", next_verify_test_number);
126
127         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
128         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
129         BOOST_REQUIRE (st->second);
130         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1", next_verify_test_number)));
131         ++st;
132         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
133         BOOST_REQUIRE (st->second);
134         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
135         ++st;
136         BOOST_CHECK_EQUAL (st->first, "Checking reel");
137         BOOST_REQUIRE (!st->second);
138         ++st;
139         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
140         BOOST_REQUIRE (st->second);
141         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/video.mxf", next_verify_test_number)));
142         ++st;
143         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
144         BOOST_REQUIRE (st->second);
145         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/audio.mxf", next_verify_test_number)));
146         ++st;
147         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
148         BOOST_REQUIRE (st->second);
149         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
150         ++st;
151         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
152         BOOST_REQUIRE (st->second);
153         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
154         ++st;
155         BOOST_REQUIRE (st == stages.end());
156
157         dump_notes (notes);
158
159         BOOST_CHECK_EQUAL (notes.size(), 0);
160
161         next_verify_test_number++;
162 }
163
164 /* Corrupt the MXFs and check that this is spotted */
165 BOOST_AUTO_TEST_CASE (verify_test2)
166 {
167         vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
168
169         FILE* mod = fopen("build/test/verify_test2/video.mxf", "r+b");
170         BOOST_REQUIRE (mod);
171         fseek (mod, 4096, SEEK_SET);
172         int x = 42;
173         fwrite (&x, sizeof(x), 1, mod);
174         fclose (mod);
175
176         mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
177         BOOST_REQUIRE (mod);
178         fseek (mod, 4096, SEEK_SET);
179         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
180         fclose (mod);
181
182         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
183
184         BOOST_REQUIRE_EQUAL (notes.size(), 2);
185         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
186         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_HASH_INCORRECT);
187         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_ERROR);
188         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::SOUND_HASH_INCORRECT);
189 }
190
191 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
192 BOOST_AUTO_TEST_CASE (verify_test3)
193 {
194         vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
195
196         {
197                 Editor e ("build/test/verify_test3/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml");
198                 e.replace ("<Hash>", "<Hash>x");
199         }
200
201         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
202
203         dump_notes (notes);
204
205         BOOST_REQUIRE_EQUAL (notes.size(), 6);
206         list<dcp::VerificationNote>::const_iterator i = notes.begin();
207         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
208         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
209         ++i;
210         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
211         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE);
212         ++i;
213         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
214         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE);
215         ++i;
216         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
217         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
218         ++i;
219         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
220         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
221         ++i;
222         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
223         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
224         ++i;
225 }
226
227 /* Corrupt the ContentKind in the CPL */
228 BOOST_AUTO_TEST_CASE (verify_test4)
229 {
230         vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
231
232         {
233                 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
234                 e.replace ("<ContentKind>", "<ContentKind>x");
235         }
236
237         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
238
239         BOOST_REQUIRE_EQUAL (notes.size(), 1);
240         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
241         BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
242 }
243
244 static
245 boost::filesystem::path
246 cpl (int n)
247 {
248         return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
249 }
250
251 static
252 boost::filesystem::path
253 pkl (int n)
254 {
255         return dcp::String::compose("build/test/verify_test%1/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml", n);
256 }
257
258 static
259 boost::filesystem::path
260 asset_map (int n)
261 {
262         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
263 }
264
265 static
266 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1)
267 {
268         vector<boost::filesystem::path> directories = setup (1, n);
269
270         {
271                 Editor e (file(n));
272                 e.replace (from, to);
273         }
274
275         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
276
277         dump_notes (notes);
278
279         BOOST_REQUIRE_EQUAL (notes.size(), 1);
280         BOOST_CHECK_EQUAL (notes.front().code(), code1);
281 }
282
283 static
284 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1, dcp::VerificationNote::Code code2)
285 {
286         vector<boost::filesystem::path> directories = setup (1, n);
287
288         {
289                 Editor e (file(n));
290                 e.replace (from, to);
291         }
292
293         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
294
295         dump_notes (notes);
296
297         BOOST_REQUIRE_EQUAL (notes.size(), 2);
298         BOOST_CHECK_EQUAL (notes.front().code(), code1);
299         BOOST_CHECK_EQUAL (notes.back().code(), code2);
300 }
301
302 static
303 void check_after_replace (
304         int n, boost::function<boost::filesystem::path (int)> file,
305         string from,
306         string to,
307         dcp::VerificationNote::Code code1,
308         dcp::VerificationNote::Code code2,
309         dcp::VerificationNote::Code code3
310         )
311 {
312         vector<boost::filesystem::path> directories = setup (1, n);
313
314         {
315                 Editor e (file(n));
316                 e.replace (from, to);
317         }
318
319         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
320
321         dump_notes (notes);
322
323         BOOST_REQUIRE_EQUAL (notes.size(), 3);
324         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
325         BOOST_CHECK_EQUAL (i->code(), code1);
326         ++i;
327         BOOST_CHECK_EQUAL (i->code(), code2);
328         ++i;
329         BOOST_CHECK_EQUAL (i->code(), code3);
330 }
331
332 /* FrameRate */
333 BOOST_AUTO_TEST_CASE (verify_test5)
334 {
335         check_after_replace (
336                         next_verify_test_number++, &cpl,
337                         "<FrameRate>24 1", "<FrameRate>99 1",
338                         dcp::VerificationNote::CPL_HASH_INCORRECT,
339                         dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE
340                         );
341 }
342
343 /* Missing asset */
344 BOOST_AUTO_TEST_CASE (verify_test6)
345 {
346         vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
347
348         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
349         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
350
351         BOOST_REQUIRE_EQUAL (notes.size(), 1);
352         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
353         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::MISSING_ASSET);
354 }
355
356 static
357 boost::filesystem::path
358 assetmap (int n)
359 {
360         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
361 }
362
363 /* Empty asset filename in ASSETMAP */
364 BOOST_AUTO_TEST_CASE (verify_test7)
365 {
366         check_after_replace (
367                         next_verify_test_number++, &assetmap,
368                         "<Path>video.mxf</Path>", "<Path></Path>",
369                         dcp::VerificationNote::EMPTY_ASSET_PATH
370                         );
371 }
372
373 /* Mismatched standard */
374 BOOST_AUTO_TEST_CASE (verify_test8)
375 {
376         check_after_replace (
377                         next_verify_test_number++, &cpl,
378                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
379                         dcp::VerificationNote::MISMATCHED_STANDARD,
380                         dcp::VerificationNote::XML_VALIDATION_ERROR,
381                         dcp::VerificationNote::CPL_HASH_INCORRECT
382                         );
383 }
384
385 /* Badly formatted <Id> in CPL */
386 BOOST_AUTO_TEST_CASE (verify_test9)
387 {
388         /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
389         check_after_replace (
390                         next_verify_test_number++, &cpl,
391                         "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
392                         dcp::VerificationNote::XML_VALIDATION_ERROR
393                         );
394 }
395
396 /* Badly formatted <IssueDate> in CPL */
397 BOOST_AUTO_TEST_CASE (verify_test10)
398 {
399         check_after_replace (
400                         next_verify_test_number++, &cpl,
401                         "<IssueDate>", "<IssueDate>x",
402                         dcp::VerificationNote::XML_VALIDATION_ERROR,
403                         dcp::VerificationNote::CPL_HASH_INCORRECT
404                         );
405 }
406
407 /* Badly-formatted <Id> in PKL */
408 BOOST_AUTO_TEST_CASE (verify_test11)
409 {
410         check_after_replace (
411                 next_verify_test_number++, &pkl,
412                 "<Id>urn:uuid:ae8", "<Id>urn:uuid:xe8",
413                 dcp::VerificationNote::XML_VALIDATION_ERROR
414                 );
415 }
416
417 /* Badly-formatted <Id> in ASSETMAP */
418 BOOST_AUTO_TEST_CASE (verify_test12)
419 {
420         check_after_replace (
421                 next_verify_test_number++, &asset_map,
422                 "<Id>urn:uuid:74e", "<Id>urn:uuid:x4e",
423                 dcp::VerificationNote::XML_VALIDATION_ERROR
424                 );
425 }
426
427 /* Basic test of an Interop DCP */
428 BOOST_AUTO_TEST_CASE (verify_test13)
429 {
430         stages.clear ();
431         vector<boost::filesystem::path> directories = setup (3, next_verify_test_number);
432         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
433
434         boost::filesystem::path const cpl_file = dcp::String::compose("build/test/verify_test%1/cpl_cbfd2bc0-21cf-4a8f-95d8-9cddcbe51296.xml", next_verify_test_number);
435         boost::filesystem::path const pkl_file = dcp::String::compose("build/test/verify_test%1/pkl_d87a950c-bd6f-41f6-90cc-56ccd673e131.xml", next_verify_test_number);
436         boost::filesystem::path const assetmap_file = dcp::String::compose("build/test/verify_test%1/ASSETMAP", next_verify_test_number);
437
438         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
439         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
440         BOOST_REQUIRE (st->second);
441         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1", next_verify_test_number)));
442         ++st;
443         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
444         BOOST_REQUIRE (st->second);
445         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
446         ++st;
447         BOOST_CHECK_EQUAL (st->first, "Checking reel");
448         BOOST_REQUIRE (!st->second);
449         ++st;
450         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
451         BOOST_REQUIRE (st->second);
452         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/j2c_c6035f97-b07d-4e1c-944d-603fc2ddc242.mxf", next_verify_test_number)));
453         ++st;
454         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
455         BOOST_REQUIRE (st->second);
456         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/pcm_69cf9eaf-9a99-4776-b022-6902208626c3.mxf", next_verify_test_number)));
457         ++st;
458         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
459         BOOST_REQUIRE (st->second);
460         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
461         ++st;
462         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
463         BOOST_REQUIRE (st->second);
464         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
465         ++st;
466         BOOST_REQUIRE (st == stages.end());
467
468         dump_notes (notes);
469
470         BOOST_CHECK_EQUAL (notes.size(), 0);
471
472         next_verify_test_number++;
473 }
474
475 /* DCP with a short asset */
476 BOOST_AUTO_TEST_CASE (verify_test14)
477 {
478         vector<boost::filesystem::path> directories = setup (8, next_verify_test_number);
479         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
480
481         dump_notes (notes);
482
483         BOOST_REQUIRE_EQUAL (notes.size(), 4);
484         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
485         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::DURATION_TOO_SMALL);
486         ++i;
487         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
488         ++i;
489         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::DURATION_TOO_SMALL);
490         ++i;
491         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
492         ++i;
493         next_verify_test_number++;
494 }
495