summaryrefslogtreecommitdiff
path: root/indra/llimage/tests/llimageworker_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llimage/tests/llimageworker_test.cpp')
-rw-r--r--indra/llimage/tests/llimageworker_test.cpp260
1 files changed, 260 insertions, 0 deletions
diff --git a/indra/llimage/tests/llimageworker_test.cpp b/indra/llimage/tests/llimageworker_test.cpp
new file mode 100644
index 0000000000..cc44696a45
--- /dev/null
+++ b/indra/llimage/tests/llimageworker_test.cpp
@@ -0,0 +1,260 @@
+/**
+ * @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 <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");
+ }
+ }
+}