Add a debug method for when there are unexpected notes.
[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);
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);
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);
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);
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);
237
238         BOOST_REQUIRE_EQUAL (notes.size(), 1);
239         BOOST_CHECK_EQUAL (notes.front().code(), code1);
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, dcp::VerificationNote::Code code2)
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);
253
254         BOOST_REQUIRE_EQUAL (notes.size(), 2);
255         BOOST_CHECK_EQUAL (notes.front().code(), code1);
256         BOOST_CHECK_EQUAL (notes.back().code(), code2);
257 }
258
259 /* FrameRate */
260 BOOST_AUTO_TEST_CASE (verify_test5)
261 {
262         check_after_replace (
263                         5, &cpl,
264                         "<FrameRate>24 1", "<FrameRate>99 1",
265                         dcp::VerificationNote::CPL_HASH_INCORRECT,
266                         dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE
267                         );
268 }
269
270 /* Missing asset */
271 BOOST_AUTO_TEST_CASE (verify_test6)
272 {
273         vector<boost::filesystem::path> directories = setup (6);
274
275         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
276         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
277
278         BOOST_REQUIRE_EQUAL (notes.size(), 1);
279         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
280         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::MISSING_ASSET);
281 }
282
283 static
284 boost::filesystem::path
285 assetmap (int n)
286 {
287         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
288 }
289
290 /* Empty asset filename in ASSETMAP */
291 BOOST_AUTO_TEST_CASE (verify_test7)
292 {
293         check_after_replace (
294                         7, &assetmap,
295                         "<Path>video.mxf</Path>", "<Path></Path>",
296                         dcp::VerificationNote::Code::EMPTY_ASSET_PATH
297                         );
298 }
299
300 /* Mismatched standard */
301 BOOST_AUTO_TEST_CASE (verify_test8)
302 {
303         check_after_replace (
304                         8, &cpl,
305                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
306                         dcp::VerificationNote::Code::MISMATCHED_STANDARD,
307                         dcp::VerificationNote::Code::CPL_HASH_INCORRECT
308                         );
309 }
310
311 /* Badly formatted <Id> in CPL */
312 BOOST_AUTO_TEST_CASE (verify_test9)
313 {
314         /* There's no CPL_HASH_INCORRECT error here because it can't find the correct hash by ID (since the ID is wrong) */
315         check_after_replace (
316                         9, &cpl,
317                         "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
318                         dcp::VerificationNote::Code::BAD_URN_UUID
319                         );
320 }
321
322 /* Badly formatted <IssueDate> in CPL */
323 BOOST_AUTO_TEST_CASE (verify_test10)
324 {
325         check_after_replace (
326                         10, &cpl,
327                         "<IssueDate>", "<IssueDate>x",
328                         dcp::VerificationNote::Code::BAD_DATE,
329                         dcp::VerificationNote::Code::CPL_HASH_INCORRECT
330                         );
331 }