2 Copyright (C) 2018-2020 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
36 #include "compose.hpp"
37 #include <boost/test/unit_test.hpp>
38 #include <boost/foreach.hpp>
39 #include <boost/algorithm/string.hpp>
48 using boost::optional;
50 static list<pair<string, optional<boost::filesystem::path> > > stages;
51 static int next_verify_test_number = 1;
54 stage (string s, optional<boost::filesystem::path> p)
56 stages.push_back (make_pair (s, p));
65 static vector<boost::filesystem::path>
66 setup (int reference_number, int verify_test_number)
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());
74 vector<boost::filesystem::path> directories;
75 directories.push_back (dcp::String::compose("build/test/verify_test%1", verify_test_number));
83 Editor (boost::filesystem::path path)
86 _content = dcp::file_to_string (_path);
91 FILE* f = fopen(_path.string().c_str(), "w");
93 fwrite (_content.c_str(), _content.length(), 1, f);
97 void replace (string a, string b)
99 boost::algorithm::replace_all (_content, a, b);
103 boost::filesystem::path _path;
104 std::string _content;
109 dump_notes (list<dcp::VerificationNote> const & notes)
111 BOOST_FOREACH (dcp::VerificationNote i, notes) {
112 std::cout << dcp::note_to_string(i) << "\n";
116 /* Check DCP as-is (should be OK) */
117 BOOST_AUTO_TEST_CASE (verify_test1)
120 vector<boost::filesystem::path> directories = setup (1, next_verify_test_number);
121 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
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);
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)));
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));
136 BOOST_CHECK_EQUAL (st->first, "Checking reel");
137 BOOST_REQUIRE (!st->second);
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)));
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)));
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));
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));
155 BOOST_REQUIRE (st == stages.end());
159 BOOST_CHECK_EQUAL (notes.size(), 0);
161 next_verify_test_number++;
164 /* Corrupt the MXFs and check that this is spotted */
165 BOOST_AUTO_TEST_CASE (verify_test2)
167 vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
169 FILE* mod = fopen("build/test/verify_test2/video.mxf", "r+b");
171 fseek (mod, 4096, SEEK_SET);
173 fwrite (&x, sizeof(x), 1, mod);
176 mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
178 fseek (mod, 4096, SEEK_SET);
179 BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
182 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
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);
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)
194 vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
197 Editor e ("build/test/verify_test3/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml");
198 e.replace ("<Hash>", "<Hash>x");
201 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
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);
210 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
211 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE);
213 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
214 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE);
216 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
217 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
219 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
220 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
222 BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
223 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
227 /* Corrupt the ContentKind in the CPL */
228 BOOST_AUTO_TEST_CASE (verify_test4)
230 vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
233 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
234 e.replace ("<ContentKind>", "<ContentKind>x");
237 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
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'");
245 boost::filesystem::path
248 return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
252 boost::filesystem::path
255 return dcp::String::compose("build/test/verify_test%1/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml", n);
259 boost::filesystem::path
262 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
266 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1)
268 vector<boost::filesystem::path> directories = setup (1, n);
272 e.replace (from, to);
275 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
279 BOOST_REQUIRE_EQUAL (notes.size(), 1);
280 BOOST_CHECK_EQUAL (notes.front().code(), code1);
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)
286 vector<boost::filesystem::path> directories = setup (1, n);
290 e.replace (from, to);
293 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
297 BOOST_REQUIRE_EQUAL (notes.size(), 2);
298 BOOST_CHECK_EQUAL (notes.front().code(), code1);
299 BOOST_CHECK_EQUAL (notes.back().code(), code2);
303 void check_after_replace (
304 int n, boost::function<boost::filesystem::path (int)> file,
307 dcp::VerificationNote::Code code1,
308 dcp::VerificationNote::Code code2,
309 dcp::VerificationNote::Code code3
312 vector<boost::filesystem::path> directories = setup (1, n);
316 e.replace (from, to);
319 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
323 BOOST_REQUIRE_EQUAL (notes.size(), 3);
324 list<dcp::VerificationNote>::const_iterator i = notes.begin ();
325 BOOST_CHECK_EQUAL (i->code(), code1);
327 BOOST_CHECK_EQUAL (i->code(), code2);
329 BOOST_CHECK_EQUAL (i->code(), code3);
333 BOOST_AUTO_TEST_CASE (verify_test5)
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
344 BOOST_AUTO_TEST_CASE (verify_test6)
346 vector<boost::filesystem::path> directories = setup (1, next_verify_test_number++);
348 boost::filesystem::remove ("build/test/verify_test6/video.mxf");
349 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
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);
357 boost::filesystem::path
360 return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
363 /* Empty asset filename in ASSETMAP */
364 BOOST_AUTO_TEST_CASE (verify_test7)
366 check_after_replace (
367 next_verify_test_number++, &assetmap,
368 "<Path>video.mxf</Path>", "<Path></Path>",
369 dcp::VerificationNote::EMPTY_ASSET_PATH
373 /* Mismatched standard */
374 BOOST_AUTO_TEST_CASE (verify_test8)
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
385 /* Badly formatted <Id> in CPL */
386 BOOST_AUTO_TEST_CASE (verify_test9)
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
396 /* Badly formatted <IssueDate> in CPL */
397 BOOST_AUTO_TEST_CASE (verify_test10)
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
407 /* Badly-formatted <Id> in PKL */
408 BOOST_AUTO_TEST_CASE (verify_test11)
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
417 /* Badly-formatted <Id> in ASSETMAP */
418 BOOST_AUTO_TEST_CASE (verify_test12)
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
427 /* Basic test of an Interop DCP */
428 BOOST_AUTO_TEST_CASE (verify_test13)
431 vector<boost::filesystem::path> directories = setup (3, next_verify_test_number);
432 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
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);
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)));
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));
447 BOOST_CHECK_EQUAL (st->first, "Checking reel");
448 BOOST_REQUIRE (!st->second);
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)));
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)));
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));
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));
466 BOOST_REQUIRE (st == stages.end());
470 BOOST_CHECK_EQUAL (notes.size(), 0);
472 next_verify_test_number++;
475 /* DCP with a short asset */
476 BOOST_AUTO_TEST_CASE (verify_test14)
478 vector<boost::filesystem::path> directories = setup (8, next_verify_test_number);
479 list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
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);
487 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
489 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::DURATION_TOO_SMALL);
491 BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::INTRINSIC_DURATION_TOO_SMALL);
493 next_verify_test_number++;