Use VerificationNote more 'properly' in a fair few places.
[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
124         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
125         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
126         BOOST_REQUIRE (st->second);
127         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1"));
128         ++st;
129         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
130         BOOST_REQUIRE (st->second);
131         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
132         ++st;
133         BOOST_CHECK_EQUAL (st->first, "Checking reel");
134         BOOST_REQUIRE (!st->second);
135         ++st;
136         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
137         BOOST_REQUIRE (st->second);
138         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
139         ++st;
140         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
141         BOOST_REQUIRE (st->second);
142         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
143         ++st;
144         BOOST_CHECK_EQUAL (st->first, "Checking PKL");
145         BOOST_REQUIRE (st->second);
146         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(pkl_file));
147         ++st;
148         BOOST_REQUIRE (st == stages.end());
149
150         dump_notes (notes);
151
152         BOOST_CHECK_EQUAL (notes.size(), 0);
153 }
154
155 /* Corrupt the MXFs and check that this is spotted */
156 BOOST_AUTO_TEST_CASE (verify_test2)
157 {
158         vector<boost::filesystem::path> directories = setup (2);
159
160         FILE* mod = fopen("build/test/verify_test2/video.mxf", "r+b");
161         BOOST_REQUIRE (mod);
162         fseek (mod, 4096, SEEK_SET);
163         int x = 42;
164         fwrite (&x, sizeof(x), 1, mod);
165         fclose (mod);
166
167         mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
168         BOOST_REQUIRE (mod);
169         fseek (mod, 4096, SEEK_SET);
170         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
171         fclose (mod);
172
173         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
174
175         BOOST_REQUIRE_EQUAL (notes.size(), 2);
176         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
177         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_HASH_INCORRECT);
178         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_ERROR);
179         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::SOUND_HASH_INCORRECT);
180 }
181
182 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
183 BOOST_AUTO_TEST_CASE (verify_test3)
184 {
185         vector<boost::filesystem::path> directories = setup (3);
186
187         {
188                 Editor e ("build/test/verify_test3/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml");
189                 e.replace ("<Hash>", "<Hash>x");
190         }
191
192         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
193
194         dump_notes (notes);
195
196         BOOST_REQUIRE_EQUAL (notes.size(), 6);
197         list<dcp::VerificationNote>::const_iterator i = notes.begin();
198         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
199         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
200         ++i;
201         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
202         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE);
203         ++i;
204         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
205         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE);
206         ++i;
207         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
208         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
209         ++i;
210         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
211         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
212         ++i;
213         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
214         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::XML_VALIDATION_ERROR);
215         ++i;
216 }
217
218 /* Corrupt the ContentKind in the CPL */
219 BOOST_AUTO_TEST_CASE (verify_test4)
220 {
221         vector<boost::filesystem::path> directories = setup (4);
222
223         {
224                 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
225                 e.replace ("<ContentKind>", "<ContentKind>x");
226         }
227
228         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
229
230         BOOST_REQUIRE_EQUAL (notes.size(), 1);
231         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
232         BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
233 }
234
235 static
236 boost::filesystem::path
237 cpl (int n)
238 {
239         return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
240 }
241
242 static
243 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1)
244 {
245         vector<boost::filesystem::path> directories = setup (n);
246
247         {
248                 Editor e (file(n));
249                 e.replace (from, to);
250         }
251
252         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
253
254         dump_notes (notes);
255
256         BOOST_REQUIRE_EQUAL (notes.size(), 1);
257         BOOST_CHECK_EQUAL (notes.front().code(), code1);
258 }
259
260 static
261 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)
262 {
263         vector<boost::filesystem::path> directories = setup (n);
264
265         {
266                 Editor e (file(n));
267                 e.replace (from, to);
268         }
269
270         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
271
272         dump_notes (notes);
273
274         BOOST_REQUIRE_EQUAL (notes.size(), 2);
275         BOOST_CHECK_EQUAL (notes.front().code(), code1);
276         BOOST_CHECK_EQUAL (notes.back().code(), code2);
277 }
278
279 static
280 void check_after_replace (
281         int n, boost::function<boost::filesystem::path (int)> file,
282         string from,
283         string to,
284         dcp::VerificationNote::Code code1,
285         dcp::VerificationNote::Code code2,
286         dcp::VerificationNote::Code code3
287         )
288 {
289         vector<boost::filesystem::path> directories = setup (n);
290
291         {
292                 Editor e (file(n));
293                 e.replace (from, to);
294         }
295
296         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
297
298         dump_notes (notes);
299
300         BOOST_REQUIRE_EQUAL (notes.size(), 3);
301         list<dcp::VerificationNote>::const_iterator i = notes.begin ();
302         BOOST_CHECK_EQUAL (i->code(), code1);
303         ++i;
304         BOOST_CHECK_EQUAL (i->code(), code2);
305         ++i;
306         BOOST_CHECK_EQUAL (i->code(), code3);
307 }
308
309 /* FrameRate */
310 BOOST_AUTO_TEST_CASE (verify_test5)
311 {
312         check_after_replace (
313                         5, &cpl,
314                         "<FrameRate>24 1", "<FrameRate>99 1",
315                         dcp::VerificationNote::CPL_HASH_INCORRECT,
316                         dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE
317                         );
318 }
319
320 /* Missing asset */
321 BOOST_AUTO_TEST_CASE (verify_test6)
322 {
323         vector<boost::filesystem::path> directories = setup (6);
324
325         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
326         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress, "xsd");
327
328         BOOST_REQUIRE_EQUAL (notes.size(), 1);
329         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
330         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::MISSING_ASSET);
331 }
332
333 static
334 boost::filesystem::path
335 assetmap (int n)
336 {
337         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
338 }
339
340 /* Empty asset filename in ASSETMAP */
341 BOOST_AUTO_TEST_CASE (verify_test7)
342 {
343         check_after_replace (
344                         7, &assetmap,
345                         "<Path>video.mxf</Path>", "<Path></Path>",
346                         dcp::VerificationNote::Code::EMPTY_ASSET_PATH
347                         );
348 }
349
350 /* Mismatched standard */
351 BOOST_AUTO_TEST_CASE (verify_test8)
352 {
353         check_after_replace (
354                         8, &cpl,
355                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
356                         dcp::VerificationNote::Code::MISMATCHED_STANDARD,
357                         dcp::VerificationNote::Code::XML_VALIDATION_ERROR,
358                         dcp::VerificationNote::Code::CPL_HASH_INCORRECT
359                         );
360 }
361
362 /* Badly formatted <Id> in CPL */
363 BOOST_AUTO_TEST_CASE (verify_test9)
364 {
365         /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
366         check_after_replace (
367                         9, &cpl,
368                         "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
369                         dcp::VerificationNote::Code::XML_VALIDATION_ERROR
370                         );
371 }
372
373 /* Badly formatted <IssueDate> in CPL */
374 BOOST_AUTO_TEST_CASE (verify_test10)
375 {
376         check_after_replace (
377                         10, &cpl,
378                         "<IssueDate>", "<IssueDate>x",
379                         dcp::VerificationNote::Code::XML_VALIDATION_ERROR,
380                         dcp::VerificationNote::Code::CPL_HASH_INCORRECT
381                         );
382 }