More adventures in the art of enum namespacing.
[libdcp.git] / test / verify_test.cc
1 /*
2     Copyright (C) 2018-2019 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
52 static void
53 stage (string s, optional<boost::filesystem::path> p)
54 {
55         stages.push_back (make_pair (s, p));
56 }
57
58 static void
59 progress (float)
60 {
61
62 }
63
64 static vector<boost::filesystem::path>
65 setup (int n)
66 {
67         boost::filesystem::remove_all (dcp::String::compose("build/test/verify_test%1", n));
68         boost::filesystem::create_directory (dcp::String::compose("build/test/verify_test%1", n));
69         for (boost::filesystem::directory_iterator i("test/ref/DCP/dcp_test1"); i != boost::filesystem::directory_iterator(); ++i) {
70                 boost::filesystem::copy_file (i->path(), dcp::String::compose("build/test/verify_test%1", n) / i->path().filename());
71         }
72
73         vector<boost::filesystem::path> directories;
74         directories.push_back (dcp::String::compose("build/test/verify_test%1", n));
75         return directories;
76
77 }
78
79 class Editor
80 {
81 public:
82         Editor (boost::filesystem::path path)
83                 : _path(path)
84         {
85                 _content = dcp::file_to_string (_path);
86         }
87
88         ~Editor ()
89         {
90                 FILE* f = fopen(_path.string().c_str(), "w");
91                 BOOST_REQUIRE (f);
92                 fwrite (_content.c_str(), _content.length(), 1, f);
93                 fclose (f);
94         }
95
96         void replace (string a, string b)
97         {
98                 boost::algorithm::replace_all (_content, a, b);
99         }
100
101 private:
102         boost::filesystem::path _path;
103         std::string _content;
104 };
105
106 static
107 void
108 dump_notes (list<dcp::VerificationNote> const & notes)
109 {
110         BOOST_FOREACH (dcp::VerificationNote i, notes) {
111                 std::cout << dcp::note_to_string(i) << "\n";
112         }
113 }
114
115 /* Check DCP as-is (should be OK) */
116 BOOST_AUTO_TEST_CASE (verify_test1)
117 {
118         vector<boost::filesystem::path> directories = setup (1);
119         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
120
121         boost::filesystem::path const cpl_file = "build/test/verify_test1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
122         boost::filesystem::path const pkl_file = "build/test/verify_test1/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml";
123         boost::filesystem::path const assetmap_file = "build/test/verify_test1/ASSETMAP.xml";
124
125         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
126         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
127         BOOST_REQUIRE (st->second);
128         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1"));
129         ++st;
130         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
131         BOOST_REQUIRE (st->second);
132         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
133         ++st;
134         BOOST_CHECK_EQUAL (st->first, "Checking reel");
135         BOOST_REQUIRE (!st->second);
136         ++st;
137         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
138         BOOST_REQUIRE (st->second);
139         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
140         ++st;
141         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
142         BOOST_REQUIRE (st->second);
143         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
144         ++st;
145         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
146         BOOST_REQUIRE (st->second);
147         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
148         ++st;
149         BOOST_CHECK_EQUAL (st->first, "Checking ASSETMAP");
150         BOOST_REQUIRE (st->second);
151         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(assetmap_file));
152         ++st;
153         BOOST_REQUIRE (st == stages.end());
154
155         dump_notes (notes);
156
157         BOOST_CHECK_EQUAL (notes.size(), 0);
158 }
159
160 /* Corrupt the MXFs and check that this is spotted */
161 BOOST_AUTO_TEST_CASE (verify_test2)
162 {
163         vector<boost::filesystem::path> directories = setup (2);
164
165         FILE* mod = fopen("build/test/verify_test2/video.mxf", "r+b");
166         BOOST_REQUIRE (mod);
167         fseek (mod, 4096, SEEK_SET);
168         int x = 42;
169         fwrite (&x, sizeof(x), 1, mod);
170         fclose (mod);
171
172         mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
173         BOOST_REQUIRE (mod);
174         fseek (mod, 4096, SEEK_SET);
175         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
176         fclose (mod);
177
178         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
179
180         BOOST_REQUIRE_EQUAL (notes.size(), 2);
181         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
182         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_HASH_INCORRECT);
183         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_ERROR);
184         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::SOUND_HASH_INCORRECT);
185 }
186
187 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
188 BOOST_AUTO_TEST_CASE (verify_test3)
189 {
190         vector<boost::filesystem::path> directories = setup (3);
191
192         {
193                 Editor e ("build/test/verify_test3/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml");
194                 e.replace ("<Hash>", "<Hash>x");
195         }
196
197         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
198
199         dump_notes (notes);
200
201         BOOST_REQUIRE_EQUAL (notes.size(), 6);
202         list<dcp::VerificationNote>::const_iterator i = notes.begin();
203         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
204         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
205         ++i;
206         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
207         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE);
208         ++i;
209         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
210         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE);
211         ++i;
212         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
213         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
214         ++i;
215         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
216         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
217         ++i;
218         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
219         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
220         ++i;
221 }
222
223 /* Corrupt the ContentKind in the CPL */
224 BOOST_AUTO_TEST_CASE (verify_test4)
225 {
226         vector<boost::filesystem::path> directories = setup (4);
227
228         {
229                 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
230                 e.replace ("<ContentKind>", "<ContentKind>x");
231         }
232
233         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
234
235         BOOST_REQUIRE_EQUAL (notes.size(), 1);
236         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
237         BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
238 }
239
240 static
241 boost::filesystem::path
242 cpl (int n)
243 {
244         return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
245 }
246
247 static
248 boost::filesystem::path
249 pkl (int n)
250 {
251         return dcp::String::compose("build/test/verify_test%1/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml", n);
252 }
253
254 static
255 boost::filesystem::path
256 asset_map (int n)
257 {
258         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
259 }
260
261 static
262 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1)
263 {
264         vector<boost::filesystem::path> directories = setup (n);
265
266         {
267                 Editor e (file(n));
268                 e.replace (from, to);
269         }
270
271         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
272
273         dump_notes (notes);
274
275         BOOST_REQUIRE_EQUAL (notes.size(), 1);
276         BOOST_CHECK_EQUAL (notes.front().code(), code1);
277 }
278
279 static
280 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)
281 {
282         vector<boost::filesystem::path> directories = setup (n);
283
284         {
285                 Editor e (file(n));
286                 e.replace (from, to);
287         }
288
289         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
290
291         dump_notes (notes);
292
293         BOOST_REQUIRE_EQUAL (notes.size(), 2);
294         BOOST_CHECK_EQUAL (notes.front().code(), code1);
295         BOOST_CHECK_EQUAL (notes.back().code(), code2);
296 }
297
298 static
299 void check_after_replace (
300         int n, boost::function<boost::filesystem::path (int)> file,
301         string from,
302         string to,
303         dcp::VerificationNote::Code code1,
304         dcp::VerificationNote::Code code2,
305         dcp::VerificationNote::Code code3
306         )
307 {
308         vector<boost::filesystem::path> directories = setup (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");
316
317         dump_notes (notes);
318
319         BOOST_REQUIRE_EQUAL (notes.size(), 3);
320         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
321         BOOST_CHECK_EQUAL (i->code(), code1);
322         ++i;
323         BOOST_CHECK_EQUAL (i->code(), code2);
324         ++i;
325         BOOST_CHECK_EQUAL (i->code(), code3);
326 }
327
328 /* FrameRate */
329 BOOST_AUTO_TEST_CASE (verify_test5)
330 {
331         check_after_replace (
332                         5, &cpl,
333                         "<FrameRate>24 1", "<FrameRate>99 1",
334                         dcp::VerificationNote::CPL_HASH_INCORRECT,
335                         dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE
336                         );
337 }
338
339 /* Missing asset */
340 BOOST_AUTO_TEST_CASE (verify_test6)
341 {
342         vector<boost::filesystem::path> directories = setup (6);
343
344         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
345         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
346
347         BOOST_REQUIRE_EQUAL (notes.size(), 1);
348         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
349         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::MISSING_ASSET);
350 }
351
352 static
353 boost::filesystem::path
354 assetmap (int n)
355 {
356         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
357 }
358
359 /* Empty asset filename in ASSETMAP */
360 BOOST_AUTO_TEST_CASE (verify_test7)
361 {
362         check_after_replace (
363                         7, &assetmap,
364                         "<Path>video.mxf</Path>", "<Path></Path>",
365                         dcp::VerificationNote::EMPTY_ASSET_PATH
366                         );
367 }
368
369 /* Mismatched standard */
370 BOOST_AUTO_TEST_CASE (verify_test8)
371 {
372         check_after_replace (
373                         8, &cpl,
374                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
375                         dcp::VerificationNote::MISMATCHED_STANDARD,
376                         dcp::VerificationNote::XML_VALIDATION_ERROR,
377                         dcp::VerificationNote::CPL_HASH_INCORRECT
378                         );
379 }
380
381 /* Badly formatted <Id> in CPL */
382 BOOST_AUTO_TEST_CASE (verify_test9)
383 {
384         /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
385         check_after_replace (
386                         9, &cpl,
387                         "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
388                         dcp::VerificationNote::XML_VALIDATION_ERROR
389                         );
390 }
391
392 /* Badly formatted <IssueDate> in CPL */
393 BOOST_AUTO_TEST_CASE (verify_test10)
394 {
395         check_after_replace (
396                         10, &cpl,
397                         "<IssueDate>", "<IssueDate>x",
398                         dcp::VerificationNote::XML_VALIDATION_ERROR,
399                         dcp::VerificationNote::CPL_HASH_INCORRECT
400                         );
401 }
402
403 /* Badly-formatted <Id> in PKL */
404 BOOST_AUTO_TEST_CASE (verify_test11)
405 {
406         check_after_replace (
407                 11, &pkl,
408                 "<Id>urn:uuid:ae8", "<Id>urn:uuid:xe8",
409                 dcp::VerificationNote::XML_VALIDATION_ERROR
410                 );
411 }
412
413 /* Badly-formatted <Id> in ASSETMAP */
414 BOOST_AUTO_TEST_CASE (verify_test12)
415 {
416         check_after_replace (
417                 12, &asset_map,
418                 "<Id>urn:uuid:74e", "<Id>urn:uuid:x4e",
419                 dcp::VerificationNote::XML_VALIDATION_ERROR
420                 );
421 }