More cleanups.
[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 /* Check DCP as-is (should be OK) */
107 BOOST_AUTO_TEST_CASE (verify_test1)
108 {
109         vector<boost::filesystem::path> directories = setup (1);
110         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
111
112         boost::filesystem::path const cpl_file = "build/test/verify_test1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
113
114         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
115         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
116         BOOST_REQUIRE (st->second);
117         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1"));
118         ++st;
119         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
120         BOOST_REQUIRE (st->second);
121         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
122         ++st;
123         BOOST_CHECK_EQUAL (st->first, "Checking reel");
124         BOOST_REQUIRE (!st->second);
125         ++st;
126         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
127         BOOST_REQUIRE (st->second);
128         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
129         ++st;
130         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
131         BOOST_REQUIRE (st->second);
132         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
133         ++st;
134         BOOST_REQUIRE (st == stages.end());
135
136         BOOST_CHECK_EQUAL (notes.size(), 0);
137 }
138
139 /* Corrupt the MXFs and check that this is spotted */
140 BOOST_AUTO_TEST_CASE (verify_test2)
141 {
142         vector<boost::filesystem::path> directories = setup (2);
143
144         FILE* mod = fopen("build/test/verify_test2/video.mxf", "r+b");
145         BOOST_REQUIRE (mod);
146         fseek (mod, 4096, SEEK_SET);
147         int x = 42;
148         fwrite (&x, sizeof(x), 1, mod);
149         fclose (mod);
150
151         mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
152         BOOST_REQUIRE (mod);
153         fseek (mod, 4096, SEEK_SET);
154         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
155         fclose (mod);
156
157         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
158
159         BOOST_REQUIRE_EQUAL (notes.size(), 2);
160         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
161         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_HASH_INCORRECT);
162         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_ERROR);
163         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::SOUND_HASH_INCORRECT);
164 }
165
166 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
167 BOOST_AUTO_TEST_CASE (verify_test3)
168 {
169         vector<boost::filesystem::path> directories = setup (3);
170
171         {
172                 Editor e ("build/test/verify_test3/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml");
173                 e.replace ("<Hash>", "<Hash>x");
174         }
175
176         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
177
178         BOOST_REQUIRE_EQUAL (notes.size(), 3);
179         list<dcp::VerificationNote>::const_iterator i = notes.begin();
180         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
181         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
182         ++i;
183         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
184         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE);
185         ++i;
186         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
187         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE);
188         ++i;
189 }
190
191 /* Corrupt the ContentKind in the CPL */
192 BOOST_AUTO_TEST_CASE (verify_test4)
193 {
194         vector<boost::filesystem::path> directories = setup (4);
195
196         {
197                 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
198                 e.replace ("<ContentKind>", "<ContentKind>x");
199         }
200
201         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
202
203         BOOST_REQUIRE_EQUAL (notes.size(), 1);
204         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
205         BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
206 }
207
208 static
209 void check_after_replace (int n, boost::filesystem::path file, string from, string to, dcp::VerificationNote::Code code1)
210 {
211         vector<boost::filesystem::path> directories = setup (n);
212
213         {
214                 Editor e (file);
215                 e.replace (from, to);
216         }
217
218         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
219
220         BOOST_REQUIRE_EQUAL (notes.size(), 1);
221         BOOST_CHECK_EQUAL (notes.front().code(), code1);
222 }
223
224 static
225 void check_after_replace (int n, boost::filesystem::path file, string from, string to, dcp::VerificationNote::Code code1, dcp::VerificationNote::Code code2)
226 {
227         vector<boost::filesystem::path> directories = setup (n);
228
229         {
230                 Editor e (file);
231                 e.replace (from, to);
232         }
233
234         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
235
236         BOOST_REQUIRE_EQUAL (notes.size(), 2);
237         BOOST_CHECK_EQUAL (notes.front().code(), code1);
238         BOOST_CHECK_EQUAL (notes.back().code(), code2);
239 }
240
241 /* FrameRate */
242 BOOST_AUTO_TEST_CASE (verify_test5)
243 {
244         check_after_replace (
245                         5,
246                         "build/test/verify_test5/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml",
247                         "<FrameRate>24 1", "<FrameRate>99 1",
248                         dcp::VerificationNote::CPL_HASH_INCORRECT,
249                         dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE
250                         );
251 }
252
253 /* Missing asset */
254 BOOST_AUTO_TEST_CASE (verify_test6)
255 {
256         vector<boost::filesystem::path> directories = setup (6);
257
258         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
259         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
260
261         BOOST_REQUIRE_EQUAL (notes.size(), 1);
262         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
263         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::MISSING_ASSET);
264 }
265
266 /* Empty asset filename in ASSETMAP */
267 BOOST_AUTO_TEST_CASE (verify_test7)
268 {
269         check_after_replace (
270                         7,
271                         "build/test/verify_test7/ASSETMAP.xml",
272                         "<Path>video.mxf</Path>", "<Path></Path>",
273                         dcp::VerificationNote::Code::EMPTY_ASSET_PATH
274                         );
275 }
276
277 /* Mismatched standard */
278 BOOST_AUTO_TEST_CASE (verify_test8)
279 {
280         check_after_replace (
281                         8,
282                         "build/test/verify_test8/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml",
283                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
284                         dcp::VerificationNote::Code::MISMATCHED_STANDARD,
285                         dcp::VerificationNote::Code::CPL_HASH_INCORRECT
286                         );
287 }
288