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