Run verification tests with a space in the XSD path to trigger an old bug.
[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 "j2k.h"
37 #include "reel.h"
38 #include "reel_mono_picture_asset.h"
39 #include "cpl.h"
40 #include "dcp.h"
41 #include "openjpeg_image.h"
42 #include "mono_picture_asset.h"
43 #include "mono_picture_asset_writer.h"
44 #include "interop_subtitle_asset.h"
45 #include "smpte_subtitle_asset.h"
46 #include "reel_subtitle_asset.h"
47 #include "compose.hpp"
48 #include "test.h"
49 #include <boost/test/unit_test.hpp>
50 #include <boost/foreach.hpp>
51 #include <boost/algorithm/string.hpp>
52 #include <cstdio>
53 #include <iostream>
54
55 using std::list;
56 using std::pair;
57 using std::string;
58 using std::vector;
59 using std::make_pair;
60 using boost::optional;
61 using boost::shared_ptr;
62
63
64 static list<pair<string, optional<boost::filesystem::path> > > stages;
65 static int next_verify_test_number = 1;
66
67 static void
68 stage (string s, optional<boost::filesystem::path> p)
69 {
70         stages.push_back (make_pair (s, p));
71 }
72
73 static void
74 progress (float)
75 {
76
77 }
78
79 static vector<boost::filesystem::path>
80 setup (int reference_number, int verify_test_number)
81 {
82         boost::filesystem::remove_all (dcp::String::compose("build/test/verify_test%1", verify_test_number));
83         boost::filesystem::create_directory (dcp::String::compose("build/test/verify_test%1", verify_test_number));
84         for (boost::filesystem::directory_iterator i(dcp::String::compose("test/ref/DCP/dcp_test%1", reference_number)); i != boost::filesystem::directory_iterator(); ++i) {
85                 boost::filesystem::copy_file (i->path(), dcp::String::compose("build/test/verify_test%1", verify_test_number) / i->path().filename());
86         }
87
88         vector<boost::filesystem::path> directories;
89         directories.push_back (dcp::String::compose("build/test/verify_test%1", verify_test_number));
90         return directories;
91
92 }
93
94
95 /** Class that can alter a file by searching and replacing strings within it.
96  *  On destruction modifies the file whose name was given to the constructor.
97  */
98 class Editor
99 {
100 public:
101         Editor (boost::filesystem::path path)
102                 : _path(path)
103         {
104                 _content = dcp::file_to_string (_path);
105         }
106
107         ~Editor ()
108         {
109                 FILE* f = fopen(_path.string().c_str(), "w");
110                 BOOST_REQUIRE (f);
111                 fwrite (_content.c_str(), _content.length(), 1, f);
112                 fclose (f);
113         }
114
115         void replace (string a, string b)
116         {
117                 boost::algorithm::replace_all (_content, a, b);
118         }
119
120 private:
121         boost::filesystem::path _path;
122         std::string _content;
123 };
124
125 static
126 void
127 dump_notes (list<dcp::VerificationNote> const & notes)
128 {
129         BOOST_FOREACH (dcp::VerificationNote i, notes) {
130                 std::cout << dcp::note_to_string(i) << "\n";
131         }
132 }
133
134 /* Check DCP as-is (should be OK) */
135 BOOST_AUTO_TEST_CASE (verify_test1)
136 {
137         stages.clear ();
138         vector<boost::filesystem::path> directories = setup (1, next_verify_test_number);
139         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
140
141         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);
142         boost::filesystem::path const pkl_file = dcp::String::compose("build/test/verify_test1/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml", next_verify_test_number);
143         boost::filesystem::path const assetmap_file = dcp::String::compose("build/test/verify_test1/ASSETMAP.xml", next_verify_test_number);
144
145         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
146         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
147         BOOST_REQUIRE (st->second);
148         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1", next_verify_test_number)));
149         ++st;
150         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
151         BOOST_REQUIRE (st->second);
152         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
153         ++st;
154         BOOST_CHECK_EQUAL (st->first, "Checking reel");
155         BOOST_REQUIRE (!st->second);
156         ++st;
157         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
158         BOOST_REQUIRE (st->second);
159         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/video.mxf", next_verify_test_number)));
160         ++st;
161         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
162         BOOST_REQUIRE (st->second);
163         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/video.mxf", next_verify_test_number)));
164         ++st;
165         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
166         BOOST_REQUIRE (st->second);
167         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1/audio.mxf", next_verify_test_number)));
168         ++st;
169         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
170         BOOST_REQUIRE (st->second);
171         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
172         ++st;
173         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
174         BOOST_REQUIRE (st->second);
175         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
176         ++st;
177         BOOST_REQUIRE (st == stages.end());
178
179         dump_notes (notes);
180
181         BOOST_CHECK_EQUAL (notes.size(), 0);
182
183         next_verify_test_number++;
184 }
185
186 /* Corrupt the MXFs and check that this is spotted */
187 BOOST_AUTO_TEST_CASE (verify_test2)
188 {
189         vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
190
191         FILE* mod = fopen("build/test/verify_test2/video.mxf", "r+b");
192         BOOST_REQUIRE (mod);
193         fseek (mod, 4096, SEEK_SET);
194         int x = 42;
195         fwrite (&x, sizeof(x), 1, mod);
196         fclose (mod);
197
198         mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
199         BOOST_REQUIRE (mod);
200         fseek (mod, 4096, SEEK_SET);
201         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
202         fclose (mod);
203
204         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
205
206         BOOST_REQUIRE_EQUAL (notes.size(), 2);
207         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
208         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_HASH_INCORRECT);
209         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_ERROR);
210         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::SOUND_HASH_INCORRECT);
211 }
212
213 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
214 BOOST_AUTO_TEST_CASE (verify_test3)
215 {
216         vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
217
218         {
219                 Editor e ("build/test/verify_test3/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml");
220                 e.replace ("<Hash>", "<Hash>x");
221         }
222
223         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
224
225         dump_notes (notes);
226
227         BOOST_REQUIRE_EQUAL (notes.size(), 6);
228         list<dcp::VerificationNote>::const_iterator i = notes.begin();
229         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
230         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
231         ++i;
232         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
233         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE);
234         ++i;
235         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
236         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE);
237         ++i;
238         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
239         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
240         ++i;
241         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
242         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
243         ++i;
244         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
245         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
246         ++i;
247 }
248
249 /* Corrupt the ContentKind in the CPL */
250 BOOST_AUTO_TEST_CASE (verify_test4)
251 {
252         vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
253
254         {
255                 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
256                 e.replace ("<ContentKind>", "<ContentKind>x");
257         }
258
259         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
260
261         BOOST_REQUIRE_EQUAL (notes.size(), 1);
262         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
263         BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
264 }
265
266 static
267 boost::filesystem::path
268 cpl (int n)
269 {
270         return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
271 }
272
273 static
274 boost::filesystem::path
275 pkl (int n)
276 {
277         return dcp::String::compose("build/test/verify_test%1/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml", n);
278 }
279
280 static
281 boost::filesystem::path
282 asset_map (int n)
283 {
284         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
285 }
286
287 static
288 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1)
289 {
290         vector<boost::filesystem::path> directories = setup (1, n);
291
292         {
293                 Editor e (file(n));
294                 e.replace (from, to);
295         }
296
297         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
298
299         dump_notes (notes);
300
301         BOOST_REQUIRE_EQUAL (notes.size(), 1);
302         BOOST_CHECK_EQUAL (notes.front().code(), code1);
303 }
304
305 static
306 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)
307 {
308         vector<boost::filesystem::path> directories = setup (1, n);
309
310         {
311                 Editor e (file(n));
312                 e.replace (from, to);
313         }
314
315         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
316
317         dump_notes (notes);
318
319         BOOST_REQUIRE_EQUAL (notes.size(), 2);
320         BOOST_CHECK_EQUAL (notes.front().code(), code1);
321         BOOST_CHECK_EQUAL (notes.back().code(), code2);
322 }
323
324 static
325 void check_after_replace (
326         int n, boost::function<boost::filesystem::path (int)> file,
327         string from,
328         string to,
329         dcp::VerificationNote::Code code1,
330         dcp::VerificationNote::Code code2,
331         dcp::VerificationNote::Code code3
332         )
333 {
334         vector<boost::filesystem::path> directories = setup (1, n);
335
336         {
337                 Editor e (file(n));
338                 e.replace (from, to);
339         }
340
341         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
342
343         dump_notes (notes);
344
345         BOOST_REQUIRE_EQUAL (notes.size(), 3);
346         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
347         BOOST_CHECK_EQUAL (i->code(), code1);
348         ++i;
349         BOOST_CHECK_EQUAL (i->code(), code2);
350         ++i;
351         BOOST_CHECK_EQUAL (i->code(), code3);
352 }
353
354 /* FrameRate */
355 BOOST_AUTO_TEST_CASE (verify_test5)
356 {
357         check_after_replace (
358                         next_verify_test_number++, &cpl,
359                         "<FrameRate>24 1", "<FrameRate>99 1",
360                         dcp::VerificationNote::CPL_HASH_INCORRECT,
361                         dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE
362                         );
363 }
364
365 /* Missing asset */
366 BOOST_AUTO_TEST_CASE (verify_test6)
367 {
368         vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
369
370         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
371         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
372
373         BOOST_REQUIRE_EQUAL (notes.size(), 1);
374         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
375         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::MISSING_ASSET);
376 }
377
378 static
379 boost::filesystem::path
380 assetmap (int n)
381 {
382         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
383 }
384
385 /* Empty asset filename in ASSETMAP */
386 BOOST_AUTO_TEST_CASE (verify_test7)
387 {
388         check_after_replace (
389                         next_verify_test_number++, &assetmap,
390                         "<Path>video.mxf</Path>", "<Path></Path>",
391                         dcp::VerificationNote::EMPTY_ASSET_PATH
392                         );
393 }
394
395 /* Mismatched standard */
396 BOOST_AUTO_TEST_CASE (verify_test8)
397 {
398         check_after_replace (
399                         next_verify_test_number++, &cpl,
400                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
401                         dcp::VerificationNote::MISMATCHED_STANDARD,
402                         dcp::VerificationNote::XML_VALIDATION_ERROR,
403                         dcp::VerificationNote::CPL_HASH_INCORRECT
404                         );
405 }
406
407 /* Badly formatted <Id> in CPL */
408 BOOST_AUTO_TEST_CASE (verify_test9)
409 {
410         /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
411         check_after_replace (
412                         next_verify_test_number++, &cpl,
413                         "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
414                         dcp::VerificationNote::XML_VALIDATION_ERROR
415                         );
416 }
417
418 /* Badly formatted <IssueDate> in CPL */
419 BOOST_AUTO_TEST_CASE (verify_test10)
420 {
421         check_after_replace (
422                         next_verify_test_number++, &cpl,
423                         "<IssueDate>", "<IssueDate>x",
424                         dcp::VerificationNote::XML_VALIDATION_ERROR,
425                         dcp::VerificationNote::CPL_HASH_INCORRECT
426                         );
427 }
428
429 /* Badly-formatted <Id> in PKL */
430 BOOST_AUTO_TEST_CASE (verify_test11)
431 {
432         check_after_replace (
433                 next_verify_test_number++, &pkl,
434                 "<Id>urn:uuid:ae8", "<Id>urn:uuid:xe8",
435                 dcp::VerificationNote::XML_VALIDATION_ERROR
436                 );
437 }
438
439 /* Badly-formatted <Id> in ASSETMAP */
440 BOOST_AUTO_TEST_CASE (verify_test12)
441 {
442         check_after_replace (
443                 next_verify_test_number++, &asset_map,
444                 "<Id>urn:uuid:74e", "<Id>urn:uuid:x4e",
445                 dcp::VerificationNote::XML_VALIDATION_ERROR
446                 );
447 }
448
449 /* Basic test of an Interop DCP */
450 BOOST_AUTO_TEST_CASE (verify_test13)
451 {
452         stages.clear ();
453         vector<boost::filesystem::path> directories = setup (3, next_verify_test_number);
454         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
455
456         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);
457         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);
458         boost::filesystem::path const assetmap_file = dcp::String::compose("build/test/verify_test%1/ASSETMAP", next_verify_test_number);
459
460         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
461         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
462         BOOST_REQUIRE (st->second);
463         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(dcp::String::compose("build/test/verify_test%1", next_verify_test_number)));
464         ++st;
465         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
466         BOOST_REQUIRE (st->second);
467         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
468         ++st;
469         BOOST_CHECK_EQUAL (st->first, "Checking reel");
470         BOOST_REQUIRE (!st->second);
471         ++st;
472         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
473         BOOST_REQUIRE (st->second);
474         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)));
475         ++st;
476         BOOST_CHECK_EQUAL (st->first, "Checking picture frame sizes");
477         BOOST_REQUIRE (st->second);
478         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)));
479         ++st;
480         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
481         BOOST_REQUIRE (st->second);
482         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)));
483         ++st;
484         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
485         BOOST_REQUIRE (st->second);
486         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
487         ++st;
488         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
489         BOOST_REQUIRE (st->second);
490         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
491         ++st;
492         BOOST_REQUIRE (st == stages.end());
493
494         dump_notes (notes);
495
496         BOOST_CHECK_EQUAL (notes.size(), 0);
497
498         next_verify_test_number++;
499 }
500
501 /* DCP with a short asset */
502 BOOST_AUTO_TEST_CASE (verify_test14)
503 {
504         vector<boost::filesystem::path> directories = setup (8, next_verify_test_number);
505         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, xsd_test);
506
507         dump_notes (notes);
508
509         BOOST_REQUIRE_EQUAL (notes.size(), 4);
510         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
511         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::DURATION_TOO_SMALL);
512         ++i;
513         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
514         ++i;
515         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::DURATION_TOO_SMALL);
516         ++i;
517         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
518         ++i;
519         next_verify_test_number++;
520 }
521
522
523 static
524 shared_ptr<dcp::OpenJPEGImage>
525 black_image ()
526 {
527         shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(dcp::Size(1998, 1080)));
528         int const pixels = 1998 * 1080;
529         for (int i = 0; i < 3; ++i) {
530                 memset (image->data(i), 0, pixels * sizeof(int));
531         }
532         return image;
533 }
534
535
536 static
537 void
538 dcp_from_frame (dcp::Data const& frame, boost::filesystem::path dir)
539 {
540         shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
541         boost::filesystem::create_directories (dir);
542         shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
543         for (int i = 0; i < 24; ++i) {
544                 writer->write (frame.data().get(), frame.size());
545         }
546         writer->finalize ();
547
548         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelMonoPictureAsset(asset, 0));
549         shared_ptr<dcp::Reel> reel(new dcp::Reel());
550         reel->add (reel_asset);
551         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
552         cpl->add (reel);
553         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
554         dcp->add (cpl);
555         dcp->write_xml (dcp::SMPTE);
556 }
557
558
559 /* DCP with an over-sized JPEG2000 frame */
560 BOOST_AUTO_TEST_CASE (verify_test15)
561 {
562         int const too_big = 1302083 * 2;
563
564         /* Compress a black image */
565         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
566         dcp::Data frame = dcp::compress_j2k (image, 100000000, 24, false, false);
567         BOOST_REQUIRE (frame.size() < too_big);
568
569         /* Place it in a bigger block with some zero padding at the end */
570         dcp::Data oversized_frame(too_big);
571         memcpy (oversized_frame.data().get(), frame.data().get(), frame.size());
572         memset (oversized_frame.data().get() + frame.size(), 0, too_big - frame.size());
573
574         boost::filesystem::path const dir("build/test/verify_test15");
575         boost::filesystem::remove_all (dir);
576         dcp_from_frame (oversized_frame, dir);
577
578         vector<boost::filesystem::path> dirs;
579         dirs.push_back (dir);
580         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
581         BOOST_REQUIRE_EQUAL (notes.size(), 1);
582         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_TOO_LARGE);
583 }
584
585
586 /* DCP with a nearly over-sized JPEG2000 frame */
587 BOOST_AUTO_TEST_CASE (verify_test16)
588 {
589         int const nearly_too_big = 1302083 * 0.98;
590
591         /* Compress a black image */
592         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
593         dcp::Data frame = dcp::compress_j2k (image, 100000000, 24, false, false);
594         BOOST_REQUIRE (frame.size() < nearly_too_big);
595
596         /* Place it in a bigger block with some zero padding at the end */
597         dcp::Data oversized_frame(nearly_too_big);
598         memcpy (oversized_frame.data().get(), frame.data().get(), frame.size());
599         memset (oversized_frame.data().get() + frame.size(), 0, nearly_too_big - frame.size());
600
601         boost::filesystem::path const dir("build/test/verify_test16");
602         boost::filesystem::remove_all (dir);
603         dcp_from_frame (oversized_frame, dir);
604
605         vector<boost::filesystem::path> dirs;
606         dirs.push_back (dir);
607         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
608         BOOST_REQUIRE_EQUAL (notes.size(), 1);
609         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_FRAME_NEARLY_TOO_LARGE);
610 }
611
612
613 /* DCP with a within-range JPEG2000 frame */
614 BOOST_AUTO_TEST_CASE (verify_test17)
615 {
616         /* Compress a black image */
617         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
618         dcp::Data frame = dcp::compress_j2k (image, 100000000, 24, false, false);
619         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
620
621         boost::filesystem::path const dir("build/test/verify_test17");
622         boost::filesystem::remove_all (dir);
623         dcp_from_frame (frame, dir);
624
625         vector<boost::filesystem::path> dirs;
626         dirs.push_back (dir);
627         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
628         BOOST_REQUIRE_EQUAL (notes.size(), 0);
629 }
630
631
632 /* DCP with valid Interop subtitles */
633 BOOST_AUTO_TEST_CASE (verify_test18)
634 {
635         boost::filesystem::path const dir("build/test/verify_test18");
636         boost::filesystem::remove_all (dir);
637         boost::filesystem::create_directories (dir);
638         boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
639         shared_ptr<dcp::InteropSubtitleAsset> asset(new dcp::InteropSubtitleAsset(dir / "subs.xml"));
640         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
641         shared_ptr<dcp::Reel> reel(new dcp::Reel());
642         reel->add (reel_asset);
643         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
644         cpl->add (reel);
645         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
646         dcp->add (cpl);
647         dcp->write_xml (dcp::INTEROP);
648
649         vector<boost::filesystem::path> dirs;
650         dirs.push_back (dir);
651         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
652         BOOST_REQUIRE_EQUAL (notes.size(), 0);
653 }
654
655
656 /* DCP with broken Interop subtitles */
657 BOOST_AUTO_TEST_CASE (verify_test19)
658 {
659         boost::filesystem::path const dir("build/test/verify_test19");
660         boost::filesystem::remove_all (dir);
661         boost::filesystem::create_directories (dir);
662         boost::filesystem::copy_file ("test/data/subs1.xml", dir / "subs.xml");
663         shared_ptr<dcp::InteropSubtitleAsset> asset(new dcp::InteropSubtitleAsset(dir / "subs.xml"));
664         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
665         shared_ptr<dcp::Reel> reel(new dcp::Reel());
666         reel->add (reel_asset);
667         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
668         cpl->add (reel);
669         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
670         dcp->add (cpl);
671         dcp->write_xml (dcp::INTEROP);
672
673         {
674                 Editor e (dir / "subs.xml");
675                 e.replace ("</ReelNumber>", "</ReelNumber><Foo></Foo>");
676         }
677
678         vector<boost::filesystem::path> dirs;
679         dirs.push_back (dir);
680         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
681         dump_notes(notes);
682         BOOST_REQUIRE_EQUAL (notes.size(), 2);
683         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
684         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
685 }
686
687
688 /* DCP with valid SMPTE subtitles */
689 BOOST_AUTO_TEST_CASE (verify_test20)
690 {
691         boost::filesystem::path const dir("build/test/verify_test20");
692         boost::filesystem::remove_all (dir);
693         boost::filesystem::create_directories (dir);
694         boost::filesystem::copy_file ("test/data/subs.mxf", dir / "subs.mxf");
695         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
696         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
697         shared_ptr<dcp::Reel> reel(new dcp::Reel());
698         reel->add (reel_asset);
699         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
700         cpl->add (reel);
701         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
702         dcp->add (cpl);
703         dcp->write_xml (dcp::SMPTE);
704
705         vector<boost::filesystem::path> dirs;
706         dirs.push_back (dir);
707         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
708         dump_notes (notes);
709         BOOST_REQUIRE_EQUAL (notes.size(), 0);
710 }
711
712
713 /* DCP with broken SMPTE subtitles */
714 BOOST_AUTO_TEST_CASE (verify_test21)
715 {
716         boost::filesystem::path const dir("build/test/verify_test21");
717         boost::filesystem::remove_all (dir);
718         boost::filesystem::create_directories (dir);
719         boost::filesystem::copy_file ("test/data/broken_smpte.mxf", dir / "subs.mxf");
720         shared_ptr<dcp::SMPTESubtitleAsset> asset(new dcp::SMPTESubtitleAsset(dir / "subs.mxf"));
721         shared_ptr<dcp::ReelAsset> reel_asset(new dcp::ReelSubtitleAsset(asset, dcp::Fraction(24, 1), 16 * 24, 0));
722         shared_ptr<dcp::Reel> reel(new dcp::Reel());
723         reel->add (reel_asset);
724         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
725         cpl->add (reel);
726         shared_ptr<dcp::DCP> dcp(new dcp::DCP(dir));
727         dcp->add (cpl);
728         dcp->write_xml (dcp::SMPTE);
729
730         vector<boost::filesystem::path> dirs;
731         dirs.push_back (dir);
732         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
733         dump_notes (notes);
734         BOOST_REQUIRE_EQUAL (notes.size(), 2);
735         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
736         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
737 }
738
739
740 /* VF */
741 BOOST_AUTO_TEST_CASE (verify_test22)
742 {
743         boost::filesystem::path const ov_dir("build/test/verify_test22_ov");
744         boost::filesystem::remove_all (ov_dir);
745         boost::filesystem::create_directories (ov_dir);
746
747         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
748         dcp::Data frame = dcp::compress_j2k (image, 100000000, 24, false, false);
749         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
750         dcp_from_frame (frame, ov_dir);
751
752         dcp::DCP ov (ov_dir);
753         ov.read ();
754
755         boost::filesystem::path const vf_dir("build/test/verify_test22_vf");
756         boost::filesystem::remove_all (vf_dir);
757         boost::filesystem::create_directories (vf_dir);
758
759         shared_ptr<dcp::Reel> reel(new dcp::Reel());
760         reel->add (ov.cpls().front()->reels().front()->main_picture());
761         shared_ptr<dcp::CPL> cpl(new dcp::CPL("hello", dcp::FEATURE));
762         cpl->add (reel);
763         dcp::DCP vf (vf_dir);
764         vf.add (cpl);
765         vf.write_xml (dcp::SMPTE);
766
767         vector<boost::filesystem::path> dirs;
768         dirs.push_back (vf_dir);
769         list<dcp::VerificationNote> notes = dcp::verify (dirs, &stage, &progress, xsd_test);
770         dump_notes (notes);
771         BOOST_REQUIRE_EQUAL (notes.size(), 1);
772         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::EXTERNAL_ASSET);
773 }
774
775