Add another verification test.
[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 /* FrameRate */
209 BOOST_AUTO_TEST_CASE (verify_test5)
210 {
211         vector<boost::filesystem::path> directories = setup (5);
212
213         boost::filesystem::path const cpl_file = "build/test/verify_test5/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
214
215         {
216                 Editor e ("build/test/verify_test5/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
217                 e.replace ("<FrameRate>24 1", "<FrameRate>99 1");
218         }
219
220         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
221
222         BOOST_REQUIRE_EQUAL (notes.size(), 2);
223         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
224         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE);
225 }
226
227 /* Missing asset */
228 BOOST_AUTO_TEST_CASE (verify_test6)
229 {
230         vector<boost::filesystem::path> directories = setup (6);
231
232         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
233         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
234
235         BOOST_REQUIRE_EQUAL (notes.size(), 1);
236         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
237         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::MISSING_ASSET);
238 }
239
240 /* Empty asset filename in ASSETMAP */
241 BOOST_AUTO_TEST_CASE (verify_test7)
242 {
243         vector<boost::filesystem::path> directories = setup (7);
244
245         {
246                 Editor e ("build/test/verify_test7/ASSETMAP.xml");
247                 e.replace ("<Path>video.mxf</Path>", "<Path></Path>");
248         }
249
250         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
251
252         BOOST_REQUIRE_EQUAL (notes.size(), 1);
253         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_WARNING);
254         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::EMPTY_ASSET_PATH);
255 }
256
257 /* Mismatched standard */
258 BOOST_AUTO_TEST_CASE (verify_test8)
259 {
260         vector<boost::filesystem::path> directories = setup (8);
261
262         {
263                 Editor e ("build/test/verify_test8/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
264                 e.replace ("http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#");
265         }
266
267         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
268
269         BOOST_REQUIRE_EQUAL (notes.size(), 2);
270         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
271         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::MISMATCHED_STANDARD);
272         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::Code::CPL_HASH_INCORRECT);
273 }
274