Add two new tests and tidy up some old ones a little.
[libdcp.git] / test / verify_test.cc
1 /*
2     Copyright (C) 2018 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/algorithm/string.hpp>
39 #include <cstdio>
40 #include <iostream>
41
42 using std::list;
43 using std::pair;
44 using std::string;
45 using std::vector;
46 using std::make_pair;
47 using boost::optional;
48
49 static list<pair<string, optional<boost::filesystem::path> > > stages;
50
51 static void
52 stage (string s, optional<boost::filesystem::path> p)
53 {
54         stages.push_back (make_pair (s, p));
55 }
56
57 static void
58 progress (float)
59 {
60
61 }
62
63 static vector<boost::filesystem::path>
64 setup (int n)
65 {
66         boost::filesystem::remove_all (dcp::String::compose("build/test/verify_test%1", n));
67         boost::filesystem::create_directory (dcp::String::compose("build/test/verify_test%1", n));
68         for (boost::filesystem::directory_iterator i("test/ref/DCP/dcp_test1"); i != boost::filesystem::directory_iterator(); ++i) {
69                 boost::filesystem::copy_file (i->path(), dcp::String::compose("build/test/verify_test%1", n) / i->path().filename());
70         }
71
72         vector<boost::filesystem::path> directories;
73         directories.push_back (dcp::String::compose("build/test/verify_test%1", n));
74         return directories;
75
76 }
77
78 class Editor
79 {
80 public:
81         Editor (boost::filesystem::path path)
82                 : _path(path)
83         {
84                 _content = dcp::file_to_string (_path);
85         }
86
87         ~Editor ()
88         {
89                 FILE* f = fopen(_path.string().c_str(), "w");
90                 BOOST_REQUIRE (f);
91                 fwrite (_content.c_str(), _content.length(), 1, f);
92                 fclose (f);
93         }
94
95         void replace (string a, string b)
96         {
97                 boost::algorithm::replace_all (_content, a, b);
98         }
99
100 private:
101         boost::filesystem::path _path;
102         std::string _content;
103 };
104
105 /* Check DCP as-is (should be OK) */
106 BOOST_AUTO_TEST_CASE (verify_test1)
107 {
108         vector<boost::filesystem::path> directories = setup (1);
109         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
110
111         boost::filesystem::path const cpl_file = "build/test/verify_test1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
112
113         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
114         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
115         BOOST_REQUIRE (st->second);
116         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1"));
117         ++st;
118         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
119         BOOST_REQUIRE (st->second);
120         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
121         ++st;
122         BOOST_CHECK_EQUAL (st->first, "Checking reel");
123         BOOST_REQUIRE (!st->second);
124         ++st;
125         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
126         BOOST_REQUIRE (st->second);
127         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
128         ++st;
129         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
130         BOOST_REQUIRE (st->second);
131         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
132         ++st;
133         BOOST_REQUIRE (st == stages.end());
134
135         BOOST_CHECK_EQUAL (notes.size(), 0);
136 }
137
138 /* Corrupt the MXFs and check that this is spotted */
139 BOOST_AUTO_TEST_CASE (verify_test2)
140 {
141         vector<boost::filesystem::path> directories = setup (2);
142
143         FILE* mod = fopen("build/test/verify_test2/video.mxf", "r+b");
144         BOOST_REQUIRE (mod);
145         fseek (mod, 4096, SEEK_SET);
146         int x = 42;
147         fwrite (&x, sizeof(x), 1, mod);
148         fclose (mod);
149
150         mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
151         BOOST_REQUIRE (mod);
152         fseek (mod, 4096, SEEK_SET);
153         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
154         fclose (mod);
155
156         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
157
158         BOOST_REQUIRE_EQUAL (notes.size(), 2);
159         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
160         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_HASH_INCORRECT);
161         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_ERROR);
162         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::SOUND_HASH_INCORRECT);
163 }
164
165 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
166 BOOST_AUTO_TEST_CASE (verify_test3)
167 {
168         vector<boost::filesystem::path> directories = setup (3);
169
170         {
171                 Editor e ("build/test/verify_test3/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml");
172                 e.replace ("<Hash>", "<Hash>x");
173         }
174
175         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
176
177         BOOST_REQUIRE_EQUAL (notes.size(), 3);
178         list<dcp::VerificationNote>::const_iterator i = notes.begin();
179         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
180         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
181         ++i;
182         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
183         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE);
184         ++i;
185         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
186         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE);
187         ++i;
188 }
189
190 /* Corrupt the ContentKind in the CPL */
191 BOOST_AUTO_TEST_CASE (verify_test4)
192 {
193         vector<boost::filesystem::path> directories = setup (4);
194
195         {
196                 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
197                 e.replace ("<ContentKind>", "<ContentKind>x");
198         }
199
200         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
201
202         BOOST_REQUIRE_EQUAL (notes.size(), 1);
203         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
204         BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
205 }
206
207 /* FrameRate */
208 BOOST_AUTO_TEST_CASE (verify_test5)
209 {
210         vector<boost::filesystem::path> directories = setup (5);
211
212         boost::filesystem::path const cpl_file = "build/test/verify_test5/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
213
214         {
215                 Editor e ("build/test/verify_test5/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
216                 e.replace ("<FrameRate>24 1", "<FrameRate>99 1");
217         }
218
219         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
220
221         BOOST_REQUIRE_EQUAL (notes.size(), 2);
222         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
223         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE);
224 }
225
226 /* Missing asset */
227 BOOST_AUTO_TEST_CASE (verify_test6)
228 {
229         vector<boost::filesystem::path> directories = setup (6);
230
231         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
232         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
233
234         BOOST_REQUIRE_EQUAL (notes.size(), 1);
235         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
236         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
237         BOOST_REQUIRE (static_cast<bool>(notes.front().note()));
238         BOOST_REQUIRE_EQUAL (notes.front().note().get(), "Missing asset video.mxf");
239 }
240
241 /* Empty asset filename in ASSETMAP */
242 BOOST_AUTO_TEST_CASE (verify_test7)
243 {
244         vector<boost::filesystem::path> directories = setup (7);
245
246         boost::filesystem::path const assetmap_file = "build/test/verify_test7/ASSETMAP.xml";
247
248         {
249                 Editor e ("build/test/verify_test7/ASSETMAP.xml");
250                 e.replace ("<Path>video.mxf</Path>", "<Path></Path>");
251         }
252
253         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
254
255         BOOST_REQUIRE_EQUAL (notes.size(), 1);
256         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
257         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
258         BOOST_REQUIRE (static_cast<bool>(notes.front().note()));
259         BOOST_REQUIRE_EQUAL (notes.front().note().get(), "Asset map path is empty for asset 1fab8bb0-cfaf-4225-ad6d-01768bc10470");
260 }
261