Fix undefined memory warning during tests.
[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), false);
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)
296 {
297         int const channels = 6;
298
299         /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
300         auto ms = make_shared<dcp::SoundAsset>(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::Standard::SMPTE);
301         if (key) {
302                 ms->set_key (*key);
303         }
304         ms->_language = language;
305         ms->set_metadata (mxf_meta);
306         shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix));
307
308         int const samples_per_frame = sample_rate / 24;
309
310         float* silence[channels];
311         for (auto i = 0; i < channels; ++i) {
312                 silence[i] = new float[samples_per_frame];
313                 memset (silence[i], 0, samples_per_frame * sizeof(float));
314         }
315
316         for (auto i = 0; i < frames; ++i) {
317                 sound_writer->write (silence, samples_per_frame);
318         }
319
320         sound_writer->finalize ();
321
322         for (auto i = 0; i < channels; ++i) {
323                 delete[] silence[i];
324         }
325
326         return ms;
327 }
328
329
330 shared_ptr<dcp::DCP>
331 make_simple (boost::filesystem::path path, int reels, int frames, dcp::Standard standard, optional<dcp::Key> key)
332 {
333         /* Some known metadata */
334         dcp::MXFMetadata mxf_meta;
335         mxf_meta.company_name = "OpenDCP";
336         mxf_meta.product_name = "OpenDCP";
337         mxf_meta.product_version = "0.0.25";
338
339         auto constexpr sample_rate = 48000;
340
341         boost::filesystem::remove_all (path);
342         boost::filesystem::create_directories (path);
343         auto d = make_shared<dcp::DCP>(path);
344         auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, standard);
345         cpl->set_annotation_text ("A Test DCP");
346         cpl->set_issuer ("OpenDCP 0.0.25");
347         cpl->set_creator ("OpenDCP 0.0.25");
348         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
349         cpl->set_content_version (
350                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
351                 );
352         cpl->set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
353         cpl->set_main_sound_sample_rate(sample_rate);
354         cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
355         cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
356         cpl->set_version_number(1);
357
358         for (int i = 0; i < reels; ++i) {
359                 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
360
361                 auto mp = simple_picture (path, suffix, frames, key);
362                 auto ms = simple_sound (path, suffix, mxf_meta, "en-US", frames, sample_rate, key);
363
364                 auto reel = make_shared<dcp::Reel>(
365                         shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
366                         shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
367                         );
368
369                 auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
370                 if (i == 0) {
371                         markers->set (dcp::Marker::FFOC, dcp::Time(0, 0, 0, 1, 24));
372                 }
373                 if (i == reels - 1) {
374                         markers->set (dcp::Marker::LFOC, dcp::Time(0, 0, 0, frames - 1, 24));
375                 }
376                 reel->add (markers);
377
378                 cpl->add (reel);
379         }
380
381         d->add (cpl);
382         return d;
383 }
384
385
386 shared_ptr<dcp::Subtitle>
387 simple_subtitle ()
388 {
389         return make_shared<dcp::SubtitleString>(
390                 optional<string>(),
391                 false,
392                 false,
393                 false,
394                 dcp::Colour(255, 255, 255),
395                 42,
396                 1,
397                 dcp::Time(0, 0, 4, 0, 24),
398                 dcp::Time(0, 0, 8, 0, 24),
399                 0.5,
400                 dcp::HAlign::CENTER,
401                 0.8,
402                 dcp::VAlign::TOP,
403                 dcp::Direction::LTR,
404                 "Hello world",
405                 dcp::Effect::NONE,
406                 dcp::Colour(255, 255, 255),
407                 dcp::Time(),
408                 dcp::Time(),
409                 0
410                 );
411 }
412
413
414 shared_ptr<dcp::ReelMarkersAsset>
415 simple_markers (int frames)
416 {
417         auto markers = make_shared<dcp::ReelMarkersAsset>(dcp::Fraction(24, 1), frames);
418         markers->set (dcp::Marker::FFOC, dcp::Time(1, 24, 24));
419         markers->set (dcp::Marker::LFOC, dcp::Time(frames - 1, 24, 24));
420         return markers;
421 }
422
423
424 shared_ptr<dcp::DCP>
425 make_simple_with_interop_subs (boost::filesystem::path path)
426 {
427         auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
428
429         auto subs = make_shared<dcp::InteropSubtitleAsset>();
430         subs->add (simple_subtitle());
431
432         boost::filesystem::create_directory (path / "subs");
433         dcp::ArrayData data(4096);
434         memset(data.data(), 0, data.size());
435         subs->add_font ("afont", data);
436         subs->write (path / "subs" / "subs.xml");
437
438         auto reel_subs = make_shared<dcp::ReelInteropSubtitleAsset>(subs, dcp::Fraction(24, 1), 240, 0);
439         dcp->cpls().front()->reels().front()->add (reel_subs);
440
441         return dcp;
442 }
443
444
445 shared_ptr<dcp::DCP>
446 make_simple_with_smpte_subs (boost::filesystem::path path)
447 {
448         auto dcp = make_simple (path, 1, 192);
449
450         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
451         subs->set_language (dcp::LanguageTag("de-DE"));
452         subs->set_start_time (dcp::Time());
453         subs->add (simple_subtitle());
454
455         subs->write (path / "subs.mxf");
456
457         auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
458         dcp->cpls().front()->reels().front()->add (reel_subs);
459
460         return dcp;
461 }
462
463
464 shared_ptr<dcp::DCP>
465 make_simple_with_interop_ccaps (boost::filesystem::path path)
466 {
467         auto dcp = make_simple (path, 1, 24, dcp::Standard::INTEROP);
468
469         auto subs = make_shared<dcp::InteropSubtitleAsset>();
470         subs->add (simple_subtitle());
471         subs->write (path / "ccap.xml");
472
473         auto reel_caps = make_shared<dcp::ReelInteropClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 240, 0);
474         dcp->cpls()[0]->reels()[0]->add (reel_caps);
475
476         return dcp;
477 }
478
479
480 shared_ptr<dcp::DCP>
481 make_simple_with_smpte_ccaps (boost::filesystem::path path)
482 {
483         auto dcp = make_simple (path, 1, 192);
484
485         auto subs = make_shared<dcp::SMPTESubtitleAsset>();
486         subs->set_language (dcp::LanguageTag("de-DE"));
487         subs->set_start_time (dcp::Time());
488         subs->add (simple_subtitle());
489         subs->write (path / "ccap.mxf");
490
491         auto reel_caps = make_shared<dcp::ReelSMPTEClosedCaptionAsset>(subs, dcp::Fraction(24, 1), 192, 0);
492         dcp->cpls()[0]->reels()[0]->add(reel_caps);
493
494         return dcp;
495 }
496
497
498 shared_ptr<dcp::OpenJPEGImage>
499 black_image (dcp::Size size)
500 {
501         auto image = make_shared<dcp::OpenJPEGImage>(size);
502         int const pixels = size.width * size.height;
503         for (int i = 0; i < 3; ++i) {
504                 memset (image->data(i), 0, pixels * sizeof(int));
505         }
506         return image;
507 }
508
509
510 shared_ptr<dcp::ReelAsset>
511 black_picture_asset (boost::filesystem::path dir, int frames)
512 {
513         auto image = black_image ();
514         auto frame = dcp::compress_j2k (image, 100000000, 24, false, false);
515         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
516
517         auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
518         asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
519         boost::filesystem::create_directories (dir);
520         auto writer = asset->start_write (dir / "pic.mxf", true);
521         for (int i = 0; i < frames; ++i) {
522                 writer->write (frame.data(), frame.size());
523         }
524         writer->finalize ();
525
526         return make_shared<dcp::ReelMonoPictureAsset>(asset, 0);
527 }
528
529
530 boost::filesystem::path
531 find_file (boost::filesystem::path dir, string filename_part)
532 {
533         boost::optional<boost::filesystem::path> found;
534         for (auto i: boost::filesystem::directory_iterator(dir)) {
535                 if (i.path().filename().string().find(filename_part) != string::npos) {
536                         BOOST_REQUIRE (!found);
537                         found = i;
538                 }
539         }
540         BOOST_REQUIRE (found);
541         return *found;
542 }
543
544
545 BOOST_GLOBAL_FIXTURE (TestConfig);