This patch has no influence if you don't have libcwd installed. Note that libcwd (http://libcwd.sourceforge.net/) is only available for linux. A default compile of libcwd does memory allocation tracking, which is too slow for everyday usage of the viewer (usable, but notably slower) and we don't need that. Configure your libcwd as follows: ./configure --prefix=/sl/usr --disable-alloc --enable-optimize Or whatever prefix you prefer (add --enable-maintainer-mode if you're compiling it from the SVN repository), add --disable-nonthreading to compile twice as fast. If you have it installed you can activate it's use by setting a few environment variables: CXXFLAGS="$(pkg-config --cflags libcwd_r)" LDFLAGS="$(pkg-config --libs libcwd_r) -lpthread" and then reconfiguring the viewer. The -lpthread is needed when using ld.gold, however, if you leave it out you might get an LDFLAGS that ends on trailing whitespaces, which doesn't work for some reason. Also, if you installed it in a non-standard place (/sl/usr above) then you need this to run the viewer (and tests): export LD_LIBRARY_PATH="/sl/usr/lib"
262 lines
11 KiB
C++
262 lines
11 KiB
C++
/**
|
|
* @file llimageworker_test.cpp
|
|
* @author Merov Linden
|
|
* @date 2009-04-28
|
|
*
|
|
* $LicenseInfo:firstyear=2006&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2006-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
// Precompiled header: almost always required for newview cpp files
|
|
#include "../llcommon/linden_common.h"
|
|
#include <list>
|
|
#include <map>
|
|
#include <algorithm>
|
|
// Class to test
|
|
#include "../llimageworker.h"
|
|
// For timer class
|
|
#include "../llcommon/lltimer.h"
|
|
// Tut header
|
|
#include "../test/lltut.h"
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// Stubbing: Declarations required to link and run the class being tested
|
|
// Notes:
|
|
// * Add here stubbed implementation of the few classes and methods used in the class to be tested
|
|
// * Add as little as possible (let the link errors guide you)
|
|
// * Do not make any assumption as to how those classes or methods work (i.e. don't copy/paste code)
|
|
// * A simulator for a class can be implemented here. Please comment and document thoroughly.
|
|
|
|
LLImageBase::LLImageBase() {}
|
|
LLImageBase::~LLImageBase() {}
|
|
void LLImageBase::dump() { }
|
|
void LLImageBase::sanityCheck() { }
|
|
void LLImageBase::deleteData() { }
|
|
U8* LLImageBase::allocateData(S32 size) { return NULL; }
|
|
U8* LLImageBase::reallocateData(S32 size) { return NULL; }
|
|
|
|
LLImageRaw::LLImageRaw(U16 width, U16 height, S8 components) { }
|
|
LLImageRaw::~LLImageRaw() { }
|
|
void LLImageRaw::deleteData() { }
|
|
U8* LLImageRaw::allocateData(S32 size) { return NULL; }
|
|
U8* LLImageRaw::reallocateData(S32 size) { return NULL; }
|
|
|
|
// End Stubbing
|
|
// -------------------------------------------------------------------------------------------
|
|
|
|
// -------------------------------------------------------------------------------------------
|
|
// TUT
|
|
// -------------------------------------------------------------------------------------------
|
|
|
|
namespace tut
|
|
{
|
|
// Test wrapper declarations
|
|
|
|
// Note: We derive the responder class for 2 reasons:
|
|
// 1. It's a pure virtual class and we can't compile without completed() being implemented
|
|
// 2. We actually need a responder to test that the thread work test completed
|
|
// We implement this making no assumption on what's done in the thread or worker
|
|
// though, just that the responder's completed() method is called in the end.
|
|
// Note on responders: responders are ref counted and *will* be deleted by the request they are
|
|
// attached to when the queued request is deleted. The recommended way of using them is to
|
|
// create them when creating a request, put a callback method in completed() and not rely on
|
|
// anything to survive in the responder object once completed() has been called. Let the request
|
|
// do the deletion and clean up itself.
|
|
class responder_test : public LLImageDecodeThread::Responder
|
|
{
|
|
public:
|
|
responder_test(bool* res)
|
|
{
|
|
done = res;
|
|
*done = false;
|
|
}
|
|
virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux)
|
|
{
|
|
*done = true;
|
|
}
|
|
private:
|
|
// This is what can be thought of as the minimal implementation of a responder
|
|
// Done will be switched to true when completed() is called and can be tested
|
|
// outside the responder. A better way of doing this is to store a callback here.
|
|
bool* done;
|
|
};
|
|
|
|
// Test wrapper declaration : decode thread
|
|
struct imagedecodethread_test
|
|
{
|
|
// Instance to be tested
|
|
LLImageDecodeThread* mThread;
|
|
|
|
// Constructor and destructor of the test wrapper
|
|
imagedecodethread_test()
|
|
{
|
|
mThread = NULL;
|
|
}
|
|
~imagedecodethread_test()
|
|
{
|
|
delete mThread;
|
|
}
|
|
};
|
|
|
|
// Test wrapper declaration : image worker
|
|
// Note: this class is not meant to be instantiated outside an LLImageDecodeThread instance
|
|
// but it's not a bad idea to get its public API a good shake as part of a thorough unit test set.
|
|
// Some gotcha with the destructor though (see below).
|
|
struct imagerequest_test
|
|
{
|
|
// Instance to be tested
|
|
LLImageDecodeThread::ImageRequest* mRequest;
|
|
bool done;
|
|
|
|
// Constructor and destructor of the test wrapper
|
|
imagerequest_test()
|
|
{
|
|
done = false;
|
|
mRequest = new LLImageDecodeThread::ImageRequest(0, 0,
|
|
LLQueuedThread::PRIORITY_NORMAL, 0, FALSE,
|
|
new responder_test(&done));
|
|
}
|
|
~imagerequest_test()
|
|
{
|
|
// We should delete the object *but*, because its destructor is protected, that cannot be
|
|
// done from outside an LLImageDecodeThread instance... So we leak memory here... It's fine...
|
|
//delete mRequest;
|
|
}
|
|
};
|
|
|
|
// Tut templating thingamagic: test group, object and test instance
|
|
typedef test_group<imagedecodethread_test> imagedecodethread_t;
|
|
typedef imagedecodethread_t::object imagedecodethread_object_t;
|
|
tut::imagedecodethread_t tut_imagedecodethread("imagedecodethread");
|
|
|
|
typedef test_group<imagerequest_test> imagerequest_t;
|
|
typedef imagerequest_t::object imagerequest_object_t;
|
|
tut::imagerequest_t tut_imagerequest("imagerequest");
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// Test functions
|
|
// Notes:
|
|
// * Test as many as you possibly can without requiring a full blown simulation of everything
|
|
// * The tests are executed in sequence so the test instance state may change between calls
|
|
// * Remember that you cannot test private methods with tut
|
|
// ---------------------------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// Test the LLImageDecodeThread interface
|
|
// ---------------------------------------------------------------------------------------
|
|
//
|
|
// Note on Unit Testing Queued Thread Classes
|
|
//
|
|
// Since methods on such a class are called on a separate loop and that we can't insert tut
|
|
// ensure() calls in there, we exercise the class with 2 sets of tests:
|
|
// - 1: Test as a single threaded instance: We declare the class but ask for no thread
|
|
// to be spawned (easy with LLThreads since there's a boolean argument on the constructor
|
|
// just for that). We can then unit test each public method like we do on a normal class.
|
|
// - 2: Test as a threaded instance: We let the thread launch and check that its external
|
|
// behavior is as expected (i.e. it runs, can accept a work order and processes
|
|
// it). Typically though there's no guarantee that this exercises all the methods of the
|
|
// class which is why we also need the previous "non threaded" set of unit tests for
|
|
// complete coverage.
|
|
//
|
|
// ---------------------------------------------------------------------------------------
|
|
|
|
template<> template<>
|
|
void imagedecodethread_object_t::test<1>()
|
|
{
|
|
// Test a *non threaded* instance of the class
|
|
mThread = new LLImageDecodeThread(false);
|
|
ensure("LLImageDecodeThread: non threaded constructor failed", mThread != NULL);
|
|
// Test that we start with an empty list right at creation
|
|
ensure("LLImageDecodeThread: non threaded init state incorrect", mThread->tut_size() == 0);
|
|
// Insert something in the queue
|
|
bool done = false;
|
|
LLImageDecodeThread::handle_t decodeHandle = mThread->decodeImage(NULL, LLQueuedThread::PRIORITY_NORMAL, 0, FALSE, new responder_test(&done));
|
|
// Verifies we got a valid handle
|
|
ensure("LLImageDecodeThread: non threaded decodeImage(), returned handle is null", decodeHandle != 0);
|
|
// Verifies that we do now have something in the queued list
|
|
ensure("LLImageDecodeThread: non threaded decodeImage() insertion in threaded list failed", mThread->tut_size() == 1);
|
|
// Trigger queue handling "manually" (on a threaded instance, this is done on the thread loop)
|
|
S32 res = mThread->update(0);
|
|
// Verifies that we successfully handled the list
|
|
ensure("LLImageDecodeThread: non threaded update() list handling test failed", res == 0);
|
|
// Verifies that the list is now empty
|
|
ensure("LLImageDecodeThread: non threaded update() list emptying test failed", mThread->tut_size() == 0);
|
|
}
|
|
|
|
template<> template<>
|
|
void imagedecodethread_object_t::test<2>()
|
|
{
|
|
// Test a *threaded* instance of the class
|
|
mThread = new LLImageDecodeThread(true);
|
|
ensure("LLImageDecodeThread: threaded constructor failed", mThread != NULL);
|
|
// Test that we start with an empty list right at creation
|
|
ensure("LLImageDecodeThread: threaded init state incorrect", mThread->tut_size() == 0);
|
|
// Insert something in the queue
|
|
bool done = false;
|
|
LLImageDecodeThread::handle_t decodeHandle = mThread->decodeImage(NULL, LLQueuedThread::PRIORITY_NORMAL, 0, FALSE, new responder_test(&done));
|
|
// Verifies we get back a valid handle
|
|
ensure("LLImageDecodeThread: threaded decodeImage(), returned handle is null", decodeHandle != 0);
|
|
// Wait a little so to simulate the main thread doing something on its main loop...
|
|
ms_sleep(500); // 500 milliseconds
|
|
// Verifies that the responder has *not* been called yet in the meantime
|
|
ensure("LLImageDecodeThread: responder creation failed", done == false);
|
|
// Ask the thread to update: that means tells the queue to check itself and creates work requests
|
|
mThread->update(1);
|
|
// Wait till the thread has time to handle the work order (though it doesn't do much per work order...)
|
|
const U32 INCREMENT_TIME = 500; // 500 milliseconds
|
|
const U32 MAX_TIME = 20 * INCREMENT_TIME; // Do the loop 20 times max, i.e. wait 10 seconds but no more
|
|
U32 total_time = 0;
|
|
while ((done == false) && (total_time < MAX_TIME))
|
|
{
|
|
ms_sleep(INCREMENT_TIME);
|
|
total_time += INCREMENT_TIME;
|
|
}
|
|
// Verifies that the responder has now been called
|
|
ensure("LLImageDecodeThread: threaded work unit not processed", done == true);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------
|
|
// Test the LLImageDecodeThread::ImageRequest interface
|
|
// ---------------------------------------------------------------------------------------
|
|
|
|
template<> template<>
|
|
void imagerequest_object_t::test<1>()
|
|
{
|
|
// Test that we start with a correct request at creation
|
|
ensure("LLImageDecodeThread::ImageRequest::ImageRequest() constructor test failed", mRequest->tut_isOK());
|
|
bool res = mRequest->processRequest();
|
|
// Verifies that we processed the request successfully
|
|
ensure("LLImageDecodeThread::ImageRequest::processRequest() processing request test failed", res == true);
|
|
// Check that we can call the finishing call safely
|
|
try {
|
|
mRequest->finishRequest(false);
|
|
} catch (...) {
|
|
fail("LLImageDecodeThread::ImageRequest::finishRequest() test failed");
|
|
}
|
|
}
|
|
}
|