Add parameter type tests on getURL()-based FSCommand.
[gnash.git] / testsuite / MovieTester.cpp
blob1da2a0b8438d06a2dc75c6800b8b326cc050dd87
1 /*
2 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
3 * Free Software Foundation, Inc.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 */
22 #include "MovieTester.h"
23 #include "GnashException.h"
24 #include "URL.h"
25 #include "noseek_fd_adapter.h"
26 #include "movie_definition.h"
27 #include "Movie.h"
28 #include "movie_root.h"
29 #include "MovieClip.h"
30 #include "MovieFactory.h"
31 #include "sound_handler.h" // for creating the "test" sound handlers
32 #include "NullSoundHandler.h"
33 #include "RGBA.h" // for rgba class (pixel checking)
34 #include "FuzzyPixel.h" // for pixel checking
35 #include "Renderer.h"
36 #include "ManualClock.h" // for use by advance
37 #include "StreamProvider.h" // for passing to RunResources
38 #include "IOChannel.h"
39 #include "swf/TagLoadersTable.h"
40 #include "swf/DefaultTagLoaders.h"
41 #include "GnashFactory.h"
43 #ifdef RENDERER_CAIRO
44 # include "Renderer_cairo.h"
45 #endif
46 #ifdef RENDERER_OPENGL
47 # include "Renderer_ogl.h"
48 #endif
49 #ifdef RENDERER_AGG
50 # include "Renderer_agg.h"
51 #endif
53 #include "MediaHandler.h"
55 #include <cstdio>
56 #include <string>
57 #include <memory> // for unique_ptr
58 #include <cmath> // for ceil
59 #include <iostream>
61 //#define SHOW_INVALIDATED_BOUNDS_ON_ADVANCE 1
63 #ifdef SHOW_INVALIDATED_BOUNDS_ON_ADVANCE
64 #include <sstream>
65 #endif
68 using std::cout;
69 using std::endl;
71 namespace gnash {
73 namespace {
74 // exp2 isn't part of standard C++, so is defined here in case the compiler
75 // doesn't supply it (e.g. in BSD)
76 inline double exp2(double x) { return std::pow(2.0, x); }
78 bool getAveragePixel(const Renderer& r, rgba& color_return, int x, int y,
79 unsigned int radius);
81 std::string toShortString(const rgba& r) {
82 std::stringstream ss;
83 ss << +r.m_r << "," << +r.m_g << "," << +r.m_b << "," << +r.m_a;
84 return ss.str();
88 MovieTester::MovieTester(const std::string& url)
90 _x(0),
91 _y(0),
92 _forceRedraw(true),
93 _samplesFetched(0)
96 #ifdef USE_MEDIA
97 // Initialize the testing media handlers
98 initTestingMediaHandlers();
99 #endif
101 #ifdef USE_SOUND
102 // Initialize the sound handler(s)
103 initTestingSoundHandlers();
104 _runResources.setSoundHandler(_sound_handler);
105 #endif
106 #ifdef USE_MEDIA
107 _runResources.setMediaHandler(_mediaHandler);
108 #endif
110 std::shared_ptr<SWF::TagLoadersTable> loaders(new SWF::TagLoadersTable());
111 addDefaultLoaders(*loaders);
113 _runResources.setTagLoaders(loaders);
115 std::shared_ptr<StreamProvider> sp(new StreamProvider(url, url));
117 _runResources.setStreamProvider(sp);
119 if ( url == "-" ) {
120 std::unique_ptr<IOChannel> in (
121 noseek_fd_adapter::make_stream(fileno(stdin))
123 _movie_def = MovieFactory::makeMovie(std::move(in), url, _runResources, false);
124 } else {
125 URL urlObj(url);
126 if ( urlObj.protocol() == "file" ) {
127 RcInitFile& rcfile = RcInitFile::getDefaultInstance();
128 const std::string& path = urlObj.path();
129 #if 1 // add the *directory* the movie was loaded from to the local sandbox path
130 size_t lastSlash = path.find_last_of('/');
131 std::string dir = path.substr(0, lastSlash+1);
132 rcfile.addLocalSandboxPath(dir);
133 log_debug(_("%s appended to local sandboxes"), dir.c_str());
134 #else // add the *file* to be loaded to the local sandbox path
135 rcfile.addLocalSandboxPath(path);
136 log_debug(_("%s appended to local sandboxes"), path.c_str());
137 #endif
139 // _url should be always set at this point...
140 _movie_def = MovieFactory::makeMovie(urlObj, _runResources,
141 NULL, false);
144 if ( ! _movie_def ) {
145 throw GnashException("Could not load movie from "+url);
148 _movie_root.reset(new movie_root(_clock, _runResources));
150 // Initialize viewport size with the one advertised in the header
151 _width = unsigned(_movie_def->get_width_pixels());
152 _height = unsigned(_movie_def->get_height_pixels());
154 // Initialize the testing renderers
155 initTestingRenderers();
157 // Now complete load of the movie
158 _movie_def->completeLoad();
159 _movie_def->ensure_frame_loaded(_movie_def->get_frame_count());
161 // Activate verbosity so that self-contained testcases are
162 // also used
163 gnash::LogFile& dbglogfile = gnash::LogFile::getDefaultInstance();
164 dbglogfile.setVerbosity(1);
166 // Finally, place the root movie on the stage ...
167 MovieClip::MovieVariables v;
168 _movie_root->init(_movie_def.get(), v);
170 // ... and render it
171 render();
174 MovieTester::~MovieTester()
176 MovieFactory::clear();
179 void
180 MovieTester::render(std::shared_ptr<Renderer> h,
181 InvalidatedRanges& invalidated_regions)
184 // This is a bit dangerous, as there isn't really support for swapping
185 // renderers during runtime; though the only problem is likely to be
186 // that CachedBitmaps are missing.
187 _runResources.setRenderer(h);
189 h->set_invalidated_regions(invalidated_regions);
191 // We call display here to simulate effect of a real run.
193 // What we're particularly interested about is
194 // proper computation of invalidated bounds, which
195 // needs clear_invalidated() to be called.
196 // display() will call clear_invalidated() on DisplayObjects
197 // actually modified so we're fine with that.
199 // Directly calling _movie->clear_invalidated() here
200 // also work currently, as invalidating the topmost
201 // movie will force recomputation of all invalidated
202 // bounds. Still, possible future changes might
203 // introduce differences, so better to reproduce
204 // real runs as close as possible, by calling display().
206 _movie_root->display();
209 void
210 MovieTester::redraw()
212 _forceRedraw=true;
213 render();
216 void
217 MovieTester::render()
219 // Get invalidated ranges and cache them
220 _invalidatedBounds.setNull();
222 _movie_root->add_invalidated_bounds(_invalidatedBounds, false);
224 #ifdef SHOW_INVALIDATED_BOUNDS_ON_ADVANCE
225 const MovieClip* r = getRootMovie();
226 std::cout << "frame " << r->get_current_frame() << ") Invalidated bounds " << _invalidatedBounds << std::endl;
227 #endif
229 // Force full redraw by using a WORLD invalidated ranges
230 InvalidatedRanges ranges = _invalidatedBounds;
231 if ( _forceRedraw ) {
232 ranges.setWorld(); // set to world if asked a full redraw
233 _forceRedraw = false; // reset to no forced redraw
236 for (TestingRenderers::const_iterator it=_testingRenderers.begin(),
237 itE=_testingRenderers.end(); it != itE; ++it) {
238 const TestingRenderer& rend = *it;
239 render(rend.getRenderer(), ranges);
242 if ( _testingRenderers.empty() ) {
243 // Make sure display is called in any case
245 // What we're particularly interested about is
246 // proper computation of invalidated bounds, which
247 // needs clear_invalidated() to be called.
248 // display() will call clear_invalidated() on DisplayObjects
249 // actually modified so we're fine with that.
251 // Directly calling _movie->clear_invalidated() here
252 // also work currently, as invalidating the topmost
253 // movie will force recomputation of all invalidated
254 // bounds. Still, possible future changes might
255 // introduce differences, so better to reproduce
256 // real runs as close as possible, by calling display().
258 _movie_root->display();
262 void
263 MovieTester::advanceClock(unsigned long ms_current)
265 _clock.advance(ms_current);
267 if ( _sound_handler ) {
269 unsigned int ms = _clock.elapsed();
271 // We need to fetch as many samples
272 // as needed for a theoretical 44100hz loop.
273 // That is 44100 samples each second.
274 // 44100/1000 = x/ms
275 // x = (44100*ms) / 1000
276 unsigned int nSamples = (441*ms) / 10;
278 // We double because sound_handler interface takes
279 // "mono" samples... (eh.. would be wise to change)
280 unsigned int toFetch = nSamples*2;
282 // Now substract what we fetched already
283 toFetch -= _samplesFetched;
285 // And update _samplesFetched..
286 _samplesFetched += toFetch;
288 log_debug("advanceClock(%d) needs to fetch %d samples", ms, toFetch);
290 std::int16_t samples[1024];
291 while (toFetch) {
292 unsigned int n = std::min(toFetch, 1024u);
293 _sound_handler->fetchSamples((std::int16_t*)&samples, n);
294 toFetch -= n;
299 void
300 MovieTester::advance(bool updateClock)
302 if ( updateClock ) {
303 // TODO: cache 'clockAdvance'
304 float fps = _movie_def->get_frame_rate();
305 unsigned long clockAdvance = long(1000/fps);
306 advanceClock(clockAdvance);
309 if (_movie_root->advance()) render();
313 void
314 MovieTester::resizeStage(int x, int y)
316 _movie_root->setDimensions(x, y);
318 if (_movie_root->getStageScaleMode() != movie_root::SCALEMODE_NOSCALE) {
319 // TODO: fix to deal with all scale modes
320 // and alignments ?
322 // set new scale value
323 float xscale = x / _movie_def->get_width_pixels();
324 float yscale = y / _movie_def->get_height_pixels();
326 if (xscale < yscale) yscale = xscale;
327 if (yscale < xscale) xscale = yscale;
329 // Scale for all renderers.
330 for (TestingRenderers::iterator it=_testingRenderers.begin(),
331 itE=_testingRenderers.end(); it != itE; ++it) {
332 TestingRenderer& rend = *it;
333 Renderer* h = rend.getRenderer().get();
334 h->set_scale(xscale, yscale);
339 const DisplayObject*
340 MovieTester::findDisplayItemByName(const MovieClip& mc,
341 const std::string& name)
343 const DisplayList& dlist = mc.getDisplayList();
344 string_table& st = getStringTable(*getObject(&mc));
345 VM& vm = getVM(*getObject(&mc));
346 return dlist.getDisplayObjectByName(st, getURI(vm, name), false);
349 const DisplayObject*
350 MovieTester::findDisplayItemByTarget(const std::string& tgt)
352 return _movie_root->findCharacterByTarget(tgt);
355 const DisplayObject*
356 MovieTester::findDisplayItemByDepth(const MovieClip& mc,
357 int depth)
359 const DisplayList& dlist = mc.getDisplayList();
360 return dlist.getDisplayObjectAtDepth(depth);
363 void
364 MovieTester::movePointerTo(int x, int y)
366 _x = x;
367 _y = y;
368 if ( _movie_root->mouseMoved(x, y) ) render();
371 void
372 MovieTester::checkPixel(int x, int y, unsigned radius, const rgba& color,
373 short unsigned tolerance, const std::string& label, bool expectFailure) const
375 if ( ! canTestRendering() ) {
376 std::stringstream ss;
377 ss << "exp:" << toShortString(color) << " ";
378 cout << "UNTESTED: NORENDERER: pix:" << x << "," << y << " exp:" <<
379 toShortString(color) << " " << label << endl;
382 FuzzyPixel exp(color, tolerance);
383 const char* X="";
384 if ( expectFailure ) X="X";
386 //std::cout <<"chekPixel(" << color << ") called" << std::endl;
388 for (TestingRenderers::const_iterator it=_testingRenderers.begin(),
389 itE=_testingRenderers.end(); it != itE; ++it) {
390 const TestingRenderer& rend = *it;
392 std::stringstream ss;
393 ss << rend.getName() <<" ";
394 ss << "pix:" << x << "," << y <<" ";
396 rgba obt_col;
398 const Renderer& handler = *rend.getRenderer();
400 if (!getAveragePixel(handler, obt_col, x, y, radius) ) {
401 ss << " is out of rendering buffer";
402 cout << X << "FAILED: " << ss.str() << " (" << label << ")" << endl;
403 continue;
406 // Find minimum tolerance as a function of BPP
408 unsigned short minRendererTolerance = 1;
409 unsigned int bpp = handler.getBitsPerPixel();
410 if ( bpp ) {
411 // UdoG: check_pixel should *always* tolerate at least 2 ^ (8 - bpp/3)
412 minRendererTolerance = int(std::ceil(exp2(8 - bpp/3)));
415 //unsigned short tol = std::max(tolerance, minRendererTolerance);
416 unsigned short tol = tolerance*minRendererTolerance;
418 ss << "exp:" << toShortString(color) << " ";
419 ss << "obt:" << toShortString(obt_col) << " ";
420 ss << "tol:" << tol;
422 FuzzyPixel obt(obt_col, tol);
423 // equality operator would use tolerance of most tolerating FuzzyPixel
424 if (exp == obt) {
425 cout << X << "PASSED: " << ss.str() << " (" << label << ")" << endl;
426 } else {
427 cout << X << "FAILED: " << ss.str() << " (" << label << ")" << endl;
432 void
433 MovieTester::pressMouseButton()
435 if ( _movie_root->mouseClick(true) ) {
436 render();
440 void
441 MovieTester::depressMouseButton()
443 if ( _movie_root->mouseClick(false) ) {
444 render();
448 void
449 MovieTester::click()
451 int wantRedraw = 0;
452 if ( _movie_root->mouseClick(true) ) ++wantRedraw;
453 if ( _movie_root->mouseClick(false) ) ++wantRedraw;
455 if ( wantRedraw ) render();
458 void
459 MovieTester::scrollMouse(int delta)
461 if (_movie_root->mouseWheel(delta)) render();
464 void
465 MovieTester::pressKey(key::code code)
467 if ( _movie_root->keyEvent(code, true) ) {
468 render();
472 void
473 MovieTester::releaseKey(key::code code)
475 if ( _movie_root->keyEvent(code, false) ) {
476 render();
480 bool
481 MovieTester::isMouseOverMouseEntity()
483 return (_movie_root->getActiveEntityUnderPointer());
486 bool
487 MovieTester::usingHandCursor()
489 DisplayObject* activeEntity = _movie_root->getActiveEntityUnderPointer();
490 if ( ! activeEntity ) return false;
492 if ( activeEntity->isSelectableTextField() ) {
493 return false; // setCursor(CURSOR_INPUT);
494 } else if ( activeEntity->allowHandCursor() ) {
495 return true; // setCursor(CURSOR_HAND);
496 } else {
497 return false; // setCursor(CURSOR_NORMAL);
501 geometry::SnappingRanges2d<int>
502 MovieTester::getInvalidatedRanges() const
504 using namespace gnash::geometry;
506 SnappingRanges2d<float> ranges = _invalidatedBounds;
508 // scale by 1/20 (twips to pixels)
509 ranges.scale(1.0/20);
511 // Convert to integer range.
512 SnappingRanges2d<int> pixranges(ranges);
514 return pixranges;
518 bool
519 MovieTester::streamingSound() const
521 if (!_sound_handler.get()) return false;
522 #ifdef USE_SOUND
523 return _sound_handler->streamingSound();
524 #endif
528 MovieTester::soundsStarted()
530 if ( ! _sound_handler.get() ) return 0;
531 #ifdef USE_SOUND
532 return _sound_handler->numSoundsStarted();
533 #endif
537 MovieTester::soundsStopped()
539 if ( ! _sound_handler.get() ) return 0;
540 return _sound_handler->numSoundsStopped();
543 void
544 MovieTester::initTestingRenderers()
546 std::shared_ptr<Renderer> handler;
548 // TODO: add support for testing multiple renderers
549 // This is tricky as requires changes in the core lib
551 #ifdef RENDERER_AGG
552 // Initialize AGG
553 static const char* aggPixelFormats[] = {
554 "RGB555", "RGB565", "RGBA16",
555 "RGB24", "BGR24", "RGBA32", "BGRA32",
556 "ARGB32", "ABGR32"
559 for (unsigned i=0; i<sizeof(aggPixelFormats)/sizeof(*aggPixelFormats); ++i) {
560 const char* pixelFormat = aggPixelFormats[i];
561 std::string name = "AGG_" + std::string(pixelFormat);
563 handler.reset( create_Renderer_agg(pixelFormat) );
564 if ( handler.get() ) {
565 //log_debug("Renderer %s initialized", name.c_str());
566 std::cout << "Renderer " << name << " initialized" << std::endl;
567 addTestingRenderer(handler, name);
568 } else {
569 std::cout << "Renderer " << name << " not supported" << std::endl;
572 #endif // RENDERER_AGG
574 #ifdef RENDERER_CAIRO
575 // Initialize Cairo
576 handler.reset(renderer::cairo::create_handler());
578 addTestingRenderer(handler, "Cairo");
579 #endif
581 #ifdef RENDERER_OPENGL
582 // Initialize opengl renderer
583 handler.reset(renderer::opengl::create_handler(false));
584 addTestingRenderer(handler, "OpenGL");
585 #endif
588 void
589 MovieTester::addTestingRenderer(std::shared_ptr<Renderer> h,
590 const std::string& name)
592 if ( ! h->initTestBuffer(_width, _height) ) {
593 std::cout << "UNTESTED: render handler " << name
594 << " doesn't support in-memory rendering "
595 << std::endl;
596 return;
599 // TODO: make the core lib support this
600 if ( ! _testingRenderers.empty() ) {
601 std::cout << "UNTESTED: can't test render handler " << name
602 << " because gnash core lib is unable to support testing of "
603 << "multiple renderers from a single process "
604 << "and we're already testing render handler "
605 << _testingRenderers.front().getName()
606 << std::endl;
607 return;
610 _testingRenderers.push_back(TestingRenderer(h, name));
612 // this will be needed till we allow run-time swapping of renderers,
613 // see above UNTESTED message...
614 _runResources.setRenderer(_testingRenderers.back().getRenderer());
617 bool
618 MovieTester::canTestVideo() const
620 if ( ! canTestSound() ) return false;
622 return true;
625 void
626 MovieTester::initTestingSoundHandlers()
628 #ifdef USE_SOUND
629 // Currently, SoundHandler can't be constructed
630 // w/out a registered MediaHandler .
631 // Should be fixed though...
632 if (_mediaHandler.get()) {
633 _sound_handler.reset(new sound::NullSoundHandler(_mediaHandler.get()));
634 } else {
635 log_error("No media handler available, "
636 "could not construct sound handler");
638 #endif // USE_SOUND
641 void
642 MovieTester::initTestingMediaHandlers()
644 #ifdef USE_MEDIA
645 // TODO: allow selection.
646 _mediaHandler.reset(media::MediaFactory::instance().get(""));
647 #endif
650 void
651 MovieTester::restart()
653 _movie_root->reset();
654 MovieClip::MovieVariables v;
655 _movie_root->init(_movie_def.get(), v);
657 // Set _movie before calling ::render
658 render();
661 float
662 MovieTester::getFrameRate() const
664 return _movie_def->get_frame_rate();
667 namespace {
669 /// Returns the average RGB color for a square block on the stage. The
670 /// width and height of the block is defined by "radius" and x/y refer
671 /// to the center of the block. radius==1 equals getPixel() and radius==0
672 /// is illegal. For even "radius" values, the center point is not exactly
673 /// defined.
674 /// The function returns false when at least one pixel of the block was
675 /// outside the main frame buffer. In that case the value in color_return
676 /// is undefined.
677 bool getAveragePixel(const Renderer& rh, rgba& color_return, int x, int y,
678 unsigned int radius)
680 assert(radius>0);
682 // optimization:
683 if (radius==1) return rh.getPixel(color_return, x, y);
685 unsigned int r=0, g=0, b=0, a=0;
687 x -= radius/2;
688 y -= radius/2;
690 int xe = x+radius;
691 int ye = y+radius;
693 rgba pixel;
695 for (int yp=y; yp<ye; yp++)
696 for (int xp=x; xp<xe; xp++) {
697 if (!rh.getPixel(pixel, xp, yp))
698 return false;
700 r += pixel.m_r;
701 g += pixel.m_g;
702 b += pixel.m_b;
703 a += pixel.m_a;
706 int pcount = radius*radius;
707 color_return.m_r = r / pcount;
708 color_return.m_g = g / pcount;
709 color_return.m_b = b / pcount;
710 color_return.m_a = a / pcount;
712 return true;
717 } // namespace gnash