Cleanup: ues BOOST_CHECK_EQUAL.
[libdcp.git] / test / test.cc
1 /*
2     Copyright (C) 2012-2020 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 #define BOOST_TEST_DYN_LINK
35 #define BOOST_TEST_MODULE libdcp_test
36 #include "compose.hpp"
37 #include "cpl.h"
38 #include "dcp.h"
39 #include "interop_subtitle_asset.h"
40 #include "file.h"
41 #include "j2k_transcode.h"
42 #include "mono_picture_asset.h"
43 #include "mono_picture_asset.h"
44 #include "openjpeg_image.h"
45 #include "picture_asset_writer.h"
46 #include "picture_asset_writer.h"
47 #include "reel.h"
48 #include "reel_asset.h"
49 #include "reel_interop_closed_caption_asset.h"
50 #include "reel_interop_subtitle_asset.h"
51 #include "reel_markers_asset.h"
52 #include "reel_mono_picture_asset.h"
53 #include "reel_mono_picture_asset.h"
54 #include "reel_smpte_closed_caption_asset.h"
55 #include "reel_smpte_subtitle_asset.h"
56 #include "reel_sound_asset.h"
57 #include "smpte_subtitle_asset.h"
58 #include "sound_asset.h"
59 #include "sound_asset_writer.h"
60 #include "test.h"
61 #include "util.h"
62 #include "warnings.h"
63 LIBDCP_DISABLE_WARNINGS
64 #include <asdcp/KM_util.h>
65 #include <asdcp/KM_prng.h>
66 LIBDCP_ENABLE_WARNINGS
67 #include <sndfile.h>
68 LIBDCP_DISABLE_WARNINGS
69 #include <libxml++/libxml++.h>
70 LIBDCP_ENABLE_WARNINGS
71 #include <boost/test/unit_test.hpp>
72 #include <cstdio>
73 #include <iostream>
74
75
76 using std::string;
77 using std::min;
78 using std::vector;
79 using std::shared_ptr;
80 using std::make_shared;
81 using boost::optional;
82
83
84 boost::filesystem::path private_test;
85 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
86
87
88 struct TestConfig
89 {
90         TestConfig()
91         {
92                 dcp::init ();
93                 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
94                         private_test = boost::unit_test::framework::master_test_suite().argv[1];
95                 }
96
97                 using namespace boost::filesystem;
98                 boost::system::error_code ec;
99                 remove_all (xsd_test, ec);
100                 boost::filesystem::create_directory (xsd_test);
101                 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
102                         copy_file (*i, xsd_test / i->path().filename());
103                 }
104         }
105 };
106
107
108 void
109 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
110 {
111         BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
112         BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
113
114         if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
115                 return;
116         }
117
118         auto whitespace_content = [](xmlpp::Node* node) {
119                 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
120                 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
121         };
122
123         auto ref_children = ref->get_children ();
124         auto test_children = test->get_children ();
125
126         auto k = ref_children.begin ();
127         auto l = test_children.begin ();
128         while (k != ref_children.end() && l != test_children.end()) {
129
130                 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
131                         ++k;
132                         continue;
133                 }
134
135                 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
136                         ++l;
137                         continue;
138                 }
139
140                 if (whitespace_content(*k) && ignore_whitespace) {
141                         ++k;
142                         continue;
143                 }
144
145                 if (whitespace_content(*l) && ignore_whitespace) {
146                         ++l;
147                         continue;
148                 }
149
150                 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
151
152                 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
153                 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
154                 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
155                 if (ref_el && test_el) {
156                         check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
157                 }
158
159                 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
160                 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
161                 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
162                 if (ref_cn && test_cn) {
163                         BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
164                 }
165
166                 ++k;
167                 ++l;
168         }
169
170         while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
171                 ++k;
172         }
173
174         while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
175                 ++l;
176         }
177
178         BOOST_REQUIRE (k == ref_children.end());
179         BOOST_REQUIRE (l == test_children.end());
180
181         auto ref_attributes = ref->get_attributes ();
182         auto test_attributes = test->get_attributes ();
183         BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
184
185         auto m = ref_attributes.begin();
186         auto n = test_attributes.begin();
187         while (m != ref_attributes.end ()) {
188                 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
189                 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
190
191                 ++m;
192                 ++n;
193         }
194 }
195
196 void
197 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
198 {
199         auto ref_parser = new xmlpp::DomParser ();
200         ref_parser->parse_memory (ref);
201         auto ref_root = ref_parser->get_document()->get_root_node ();
202         auto test_parser = new xmlpp::DomParser ();
203         test_parser->parse_memory (test);
204         auto test_root = test_parser->get_document()->get_root_node ();
205
206         check_xml (ref_root, test_root, ignore, ignore_whitespace);
207 }
208
209 void
210 check_file (boost::filesystem::path ref, boost::filesystem::path check)
211 {
212         uintmax_t size = boost::filesystem::file_size (ref);
213         BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
214         dcp::File ref_file(ref, "rb");
215         BOOST_REQUIRE (ref_file);
216         dcp::File check_file(check, "rb");
217         BOOST_REQUIRE (check_file);
218
219         int const buffer_size = 65536;
220         std::vector<uint8_t> ref_buffer(buffer_size);
221         std::vector<uint8_t> check_buffer(buffer_size);
222
223         uintmax_t pos = 0;
224
225         while (pos < size) {
226                 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
227                 size_t r = ref_file.read(ref_buffer.data(), 1, this_time);
228                 BOOST_CHECK_EQUAL (r, this_time);
229                 r = check_file.read(check_buffer.data(), 1, this_time);
230                 BOOST_CHECK_EQUAL (r, this_time);
231
232                 if (memcmp(ref_buffer.data(), check_buffer.data(), this_time) != 0) {
233                         for (int i = 0; i < buffer_size; ++i) {
234                                 if (ref_buffer[i] != check_buffer[i]) {
235                                         BOOST_CHECK_MESSAGE (
236                                                 false,
237                                                 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
238                                                 );
239                                         break;
240                                 }
241                         }
242                         break;
243                 }
244
245                 pos += this_time;
246         }
247 }
248
249
250 RNGFixer::RNGFixer ()
251 {
252         Kumu::cth_test = true;
253         Kumu::FortunaRNG().Reset();
254 }
255
256
257 RNGFixer::~RNGFixer ()
258 {
259         Kumu::cth_test = false;
260 }
261
262
263 shared_ptr<dcp::MonoPictureAsset>
264 simple_picture (boost::filesystem::path path, string suffix, int frames, optional<dcp::Key> key)
265 {
266         dcp::MXFMetadata mxf_meta;
267         mxf_meta.company_name = "OpenDCP";
268         mxf_meta.product_name = "OpenDCP";
269         mxf_meta.product_version = "0.0.25";
270
271         auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction (24, 1), dcp::Standard::SMPTE);
272         mp->set_metadata (mxf_meta);
273         if (key) {
274                 mp->set_key (*key);
275         }
276         auto picture_writer = mp->start_write(path / dcp::String::compose("video%1.mxf", suffix), dcp::PictureAsset::Behaviour::MAKE_NEW);
277
278         dcp::Size const size (1998, 1080);
279         auto image = make_shared<dcp::OpenJPEGImage>(size);
280         for (int i = 0; i < 3; ++i) {
281                 memset (image->data(i), 0, 2 * size.width * size.height);
282         }
283         auto j2c = dcp::compress_j2k (image, 100000000, 24, false, false);
284
285         for (int i = 0; i < frames; ++i) {
286                 picture_writer->write (j2c.data(), j2c.size());
287         }
288         picture_writer->finalize ();
289
290         return mp;
291 }
292
293
294 shared_ptr<dcp::SoundAsset>
295 simple_sound(boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate, optional<dcp::Key> key, int channels)
296 {
297         /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
298         auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE);
299         if (key) {
300                 ms->set_key (*key);
301         }
302         ms->_language = language;
303         ms->set_metadata (mxf_meta);
304         auto sound_writer = ms->start_write(path / dcp::String::compose("audio%1.mxf", suffix), {}, dcp::SoundAsset::AtmosSync::DISABLED, dcp::SoundAsset::MCASubDescriptors::ENABLED);
305
306         int const samples_per_frame = sample_rate / 24;
307
308         float* silence[channels];
309         for (auto i = 0; i < channels; ++i) {
310                 silence[i] = new float[samples_per_frame];
311                 memset (silence[i], 0, samples_per_frame * sizeof(float));
312         }
313
314         for (auto i = 0; i < frames; ++i) {
315                 sound_writer->write(silence, channels, samples_per_frame);
316         }
317
318         sound_writer->finalize ();
319
320         for (auto i = 0; i < channels; ++i) {
321                 delete[] silence[i];
322         }
323
324         return ms;
325 }
326
327
328 shared_ptr<dcp::DCP>
329 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard, optional<dcp::Key> key)
330 {
331         /* Some known metadata */
332         dcp::MXFMetadata mxf_meta;
333         mxf_meta.company_name = "OpenDCP";
334         mxf_meta.product_name = "OpenDCP";
335         mxf_meta.product_version = "0.0.25";
336
337         auto constexpr sample_rate = 48000;
338
339         boost::filesystem::remove_all (path);
340         boost::filesystem::create_directories (path);
341         auto d = make_shared<dcp::DCP>(path);
342         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
343         cpl->set_annotation_text ("A Test DCP");
344         cpl->set_issuer ("OpenDCP 0.0.25");
345         cpl->set_creator ("OpenDCP 0.0.25");
346         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
347         cpl->set_content_version (
348                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
349                 );
350         cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
351         cpl->set_main_sound_sample_rate(sample_rate);
352         cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
353         cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
354         cpl->set_version_number(1);
355
356         for (int i = 0; i < reels; ++i) {
357                 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
358
359                 auto mp = simple_picture (path, suffix, frames, key);
360                 auto ms = simple_sound (path, suffix, mxf_meta, "en-US", frames, sample_rate, key);
361
362                 auto reel = make_shared<dcp::Reel>(
363                         shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
364                         shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
365                         );
366
367                 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
368                 if (i == 0) {
369                         markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
370                 }
371                 if (i == reels - 1) {
372                         markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
373                 }
374                 reel->add (markers);
375
376                 cpl->add (reel);
377         }
378
379         d->add (cpl);
380         return d;
381 }
382
383
384 shared_ptr<dcp::Subtitle>
385 simple_subtitle ()
386 {
387         return std::make_shared<dcp::SubtitleString>(
388                 optional<string>(),
389                 false,
390                 false,
391                 false,
392                 dcp::Colour(255, 255, 255),
393                 42,
394                 1,
395                 dcp::Time(0, 0, 4, 0, 24),
396                 dcp::Time(0, 0, 8, 0, 24),
397                 0.5,
398                 dcp::HAlign::CENTER,
399                 0.8,
400                 dcp::VAlign::TOP,
401                 0,
402                 dcp::Direction::LTR,
403                 "Hello world",
404                 dcp::Effect::NONE,
405                 dcp::Colour(255, 255, 255),
406                 dcp::Time(),
407                 dcp::Time(),
408                 0
409                 );
410 }
411
412
413 shared_ptr<dcp::ReelMarkersAsset>
414 simple_markers (int frames)
415 {
416         auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
417         markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
418         markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
419         return markers;
420 }
421
422
423 shared_ptr<dcp::DCP>
424 make_simple_with_interop_subs (boost::filesystem::path path)
425 {
426         auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
427
428         auto subs = make_shared<dcp::InteropSubtitleAsset>();
429         subs->add (simple_subtitle());
430
431         boost::filesystem::create_directory (path / "subs");
432         dcp::ArrayData data(4096);
433         memset(data.data(), 0, data.size());
434         subs->add_font ("afont", data);
435         subs->write (path / "subs" / "subs.xml");
436
437         auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
438         dcp->cpls().front()->reels().front()->add (reel_subs);
439
440         return dcp;
441 }
442
443
444 shared_ptr<dcp::DCP>
445 make_simple_with_smpte_subs (boost::filesystem::path path)
446 {
447         auto dcp = make_simple (path, 1, 192);
448
449         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
450         subs->set_language (dcp::LanguageTag("de-DE"));
451         subs->set_start_time (dcp::Time());
452         subs->add (simple_subtitle());
453
454         subs->write (path / "subs.mxf");
455
456         auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
457         dcp->cpls().front()->reels().front()->add (reel_subs);
458
459         return dcp;
460 }
461
462
463 shared_ptr<dcp::DCP>
464 make_simple_with_interop_ccaps (boost::filesystem::path path)
465 {
466         auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
467
468         auto subs = make_shared<dcp::InteropSubtitleAsset>();
469         subs->add (simple_subtitle());
470         subs->write (path / "ccap.xml");
471
472         auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
473         dcp->cpls()[0]->reels()[0]->add (reel_caps);
474
475         return dcp;
476 }
477
478
479 shared_ptr<dcp::DCP>
480 make_simple_with_smpte_ccaps (boost::filesystem::path path)
481 {
482         auto dcp = make_simple (path, 1, 192);
483
484         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
485         subs->set_language (dcp::LanguageTag("de-DE"));
486         subs->set_start_time (dcp::Time());
487         subs->add (simple_subtitle());
488         subs->write (path / "ccap.mxf");
489
490         auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
491         dcp->cpls()[0]->reels()[0]->add(reel_caps);
492
493         return dcp;
494 }
495
496
497 shared_ptr<dcp::OpenJPEGImage>
498 black_image (dcp::Size size)
499 {
500         auto image = make_shared<dcp::OpenJPEGImage>(size);
501         int const pixels = size.width * size.height;
502         for (int i = 0; i < 3; ++i) {
503                 memset (image->data(i), 0, pixels * sizeof(int));
504         }
505         return image;
506 }
507
508
509 shared_ptr<dcp::ReelAsset>
510 black_picture_asset (boost::filesystem::path dir, int frames)
511 {
512         auto image = black_image ();
513         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
514         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
515
516         auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
517         asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
518         boost::filesystem::create_directories (dir);
519         auto writer = asset->start_write(dir / "pic.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
520         for (int i = 0; i < frames; ++i) {
521                 writer->write (frame.data(), frame.size());
522         }
523         writer->finalize ();
524
525         return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
526 }
527
528
529 boost::filesystem::path
530 find_file (boost::filesystem::path dir, string filename_part)
531 {
532         boost::optional<boost::filesystem::path> found;
533         for (auto i: boost::filesystem::directory_iterator(dir)) {
534                 if (i.path().filename().string().find(filename_part) != string::npos) {
535                         BOOST_REQUIRE (!found);
536                         found = i;
537                 }
538         }
539         BOOST_REQUIRE (found);
540         return *found;
541 }
542
543
544 BOOST_GLOBAL_FIXTURE (TestConfig);