// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PPAPI_TESTS_TEST_UTILS_H_
#define PPAPI_TESTS_TEST_UTILS_H_
#include <string>
#include "ppapi/c/pp_instance.h"
#include "ppapi/c/pp_stdint.h"
#include "ppapi/c/private/ppb_testing_private.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/message_loop.h"
#include "ppapi/utility/completion_callback_factory.h"
namespace pp {
class NetAddress;
class URLLoader;
class URLRequestInfo;
}
// Timeout to wait for some action to complete.
extern const int kActionTimeoutMs;
const PPB_Testing_Private* GetTestingInterface();
std::string ReportError(const char* method, int32_t error);
void PlatformSleep(int duration_ms);
// Returns the host and port of the current document's URL (Which is generally
// served by an EmbeddedTestServer). Returns false on failure.
bool GetLocalHostPort(PP_Instance instance, std::string* host, uint16_t* port);
uint16_t ConvertFromNetEndian16(uint16_t x);
uint16_t ConvertToNetEndian16(uint16_t x);
bool EqualNetAddress(const pp::NetAddress& addr1, const pp::NetAddress& addr2);
// Only returns the first address if there are more than one available.
bool ResolveHost(PP_Instance instance,
const std::string& host,
uint16_t port,
pp::NetAddress* addr);
bool ReplacePort(PP_Instance instance,
const pp::NetAddress& input_addr,
uint16_t port,
pp::NetAddress* output_addr);
uint16_t GetPort(const pp::NetAddress& addr);
// NestedEvent allows you to run a nested MessageLoop and wait for a particular
// event to complete. For example, you can use it to wait for a callback on a
// PPP interface, which will "Signal" the event and make the loop quit.
// "Wait()" will return immediately if it has already been signalled. Otherwise,
// it will run a nested run loop (using PPB_Testing.RunMessageLoop) and will
// return only after it has been signalled.
// Example:
// std::string TestFullscreen::TestNormalToFullscreen() {
// pp::Fullscreen screen_mode(instance);
// screen_mode.SetFullscreen(true);
// SimulateUserGesture();
// // Let DidChangeView run in a nested run loop.
// nested_event_.Wait();
// Pass();
// }
//
// void TestFullscreen::DidChangeView(const pp::View& view) {
// nested_event_.Signal();
// }
//
// All methods except Signal and PostSignal must be invoked on the main thread.
// It's OK to signal from a background thread, so you can (for example) Signal()
// from the Audio thread.
class NestedEvent {
public:
explicit NestedEvent(PP_Instance instance)
: instance_(instance), waiting_(false), signalled_(false) {
}
// Run a nested run loop and wait until Signal() is called. If Signal()
// has already been called, return immediately without running a nested loop.
void Wait();
// Signal the NestedEvent. If Wait() has been called, quit the message loop.
// This can be called from any thread.
void Signal();
// Signal the NestedEvent in |wait_ms| milliseconds. This can be called from
// any thread.
void PostSignal(int32_t wait_ms);
// Reset the NestedEvent so it can be used again.
void Reset();
private:
void SignalOnMainThread();
static void SignalThunk(void* async_event, int32_t result);
PP_Instance instance_;
bool waiting_;
bool signalled_;
// Disable copy and assign.
NestedEvent(const NestedEvent&);
NestedEvent& operator=(const NestedEvent&);
};
// Returns a callback that does nothing, so can be invoked when the current
// function is out of scope, unlike TestCompletionCallback.
pp::CompletionCallback DoNothingCallback();
template <typename OutputT>
void DeleteStorage(void* user_data, int32_t flags) {
typename pp::CompletionCallbackWithOutput<OutputT>::OutputStorageType*
storage = reinterpret_cast<typename pp::CompletionCallbackWithOutput<
OutputT>::OutputStorageType*>(user_data);
delete storage;
}
// Same as DoNothingCallback(), but with an OutputStorageType, which it deletes
// when the callback is invoked.
template <typename OutputT>
pp::CompletionCallbackWithOutput<OutputT> DoNothingCallbackWithOutput() {
typename pp::CompletionCallbackWithOutput<OutputT>::OutputStorageType*
storage = new
typename pp::CompletionCallbackWithOutput<OutputT>::OutputStorageType();
return pp::CompletionCallbackWithOutput<OutputT>(
&DeleteStorage<OutputT>, storage, PP_COMPLETIONCALLBACK_FLAG_OPTIONAL,
storage);
}
enum CallbackType { PP_REQUIRED, PP_OPTIONAL, PP_BLOCKING };
class TestCompletionCallback {
public:
class Delegate {
public:
virtual ~Delegate() {}
virtual void OnCallback(void* user_data, int32_t result) = 0;
};
explicit TestCompletionCallback(PP_Instance instance);
// TODO(dmichael): Remove this constructor.
TestCompletionCallback(PP_Instance instance, bool force_async);
TestCompletionCallback(PP_Instance instance, CallbackType callback_type);
// Sets a Delegate instance. OnCallback() of this instance will be invoked
// when the completion callback is invoked.
// The delegate will be reset when Reset() or GetCallback() is called.
void SetDelegate(Delegate* delegate) { delegate_ = delegate; }
// Wait for a result, given the return from the call which took this callback
// as a parameter. If |result| is PP_OK_COMPLETIONPENDING, WaitForResult will
// block until its callback has been invoked (in some cases, this will already
// have happened, and WaitForCallback can return immediately).
// For any other values, WaitForResult will simply set its internal "result_"
// field. To retrieve the final result of the operation (i.e., the result
// the callback has run, if necessary), call result(). You can call result()
// as many times as necessary until a new pp::CompletionCallback is retrieved.
//
// In some cases, you may want to check that the callback was invoked in the
// expected way (i.e., if the callback was "Required", then it should be
// invoked asynchronously). Within the body of a test (where returning a non-
// empty string indicates test failure), you can use the
// CHECK_CALLBACK_BEHAVIOR(callback) macro. From within a helper function,
// you can use failed() and errors().
//
// Example usage within a test:
// callback.WaitForResult(foo.DoSomething(callback));
// CHECK_CALLBACK_BEHAVIOR(callback);
// ASSERT_EQ(PP_OK, callback.result());
//
// Example usage within a helper function:
// void HelperFunction(std::string* error_message) {
// callback.WaitForResult(foo.DoSomething(callback));
// if (callback.failed())
// error_message->assign(callback.errors());
// }
void WaitForResult(int32_t result);
// Used when you expect to receive either synchronous completion with PP_OK
// or a PP_ERROR_ABORTED asynchronously.
// Example usage:
// int32_t result = 0;
// {
// pp::URLLoader temp(instance_);
// result = temp.Open(request, callback);
// }
// callback.WaitForAbortResult(result);
// CHECK_CALLBACK_BEHAVIOR(callback);
void WaitForAbortResult(int32_t result);
// Retrieve a pp::CompletionCallback for use in testing. This Reset()s the
// TestCompletionCallback.
pp::CompletionCallback GetCallback();
bool failed() { return !errors_.empty(); }
const std::string& errors() { return errors_; }
int32_t result() const { return result_; }
// Reset so that this callback can be used again.
void Reset();
CallbackType callback_type() { return callback_type_; }
void set_target_loop(const pp::MessageLoop& loop) { target_loop_ = loop; }
static void Handler(void* user_data, int32_t result);
protected:
void RunMessageLoop();
void QuitMessageLoop();
// Used to check that WaitForResult is only called once for each usage of the
// callback.
bool wait_for_result_called_;
// Indicates whether we have already been invoked.
bool have_result_;
// The last result received (or PP_OK_COMPLETIONCALLBACK if none).
int32_t result_;
CallbackType callback_type_;
bool post_quit_task_;
std::string errors_;
PP_Instance instance_;
Delegate* delegate_;
pp::MessageLoop target_loop_;
};
template <typename OutputT>
class TestCompletionCallbackWithOutput {
public:
explicit TestCompletionCallbackWithOutput(PP_Instance instance)
: callback_(instance),
output_storage_() {
pp::internal::CallbackOutputTraits<OutputT>::Initialize(&output_storage_);
}
TestCompletionCallbackWithOutput(PP_Instance instance, bool force_async)
: callback_(instance, force_async),
output_storage_() {
pp::internal::CallbackOutputTraits<OutputT>::Initialize(&output_storage_);
}
TestCompletionCallbackWithOutput(PP_Instance instance,
CallbackType callback_type)
: callback_(instance, callback_type),
output_storage_() {
pp::internal::CallbackOutputTraits<OutputT>::Initialize(&output_storage_);
}
pp::CompletionCallbackWithOutput<OutputT> GetCallback();
OutputT output() {
return pp::internal::CallbackOutputTraits<OutputT>::StorageToPluginArg(
output_storage_);
}
// Delegate functions to TestCompletionCallback
void SetDelegate(TestCompletionCallback::Delegate* delegate) {
callback_.SetDelegate(delegate);
}
void WaitForResult(int32_t result) { callback_.WaitForResult(result); }
void WaitForAbortResult(int32_t result) {
callback_.WaitForAbortResult(result);
}
bool failed() { return callback_.failed(); }
const std::string& errors() { return callback_.errors(); }
int32_t result() const { return callback_.result(); }
void Reset() {
pp::internal::CallbackOutputTraits<OutputT>::Initialize(&output_storage_);
return callback_.Reset();
}
private:
TestCompletionCallback callback_;
typename pp::CompletionCallbackWithOutput<OutputT>::OutputStorageType
output_storage_;
};
template <typename OutputT>
pp::CompletionCallbackWithOutput<OutputT>
TestCompletionCallbackWithOutput<OutputT>::GetCallback() {
this->Reset();
if (callback_.callback_type() == PP_BLOCKING) {
pp::CompletionCallbackWithOutput<OutputT> cc(&output_storage_);
return cc;
}
callback_.set_target_loop(pp::MessageLoop::GetCurrent());
pp::CompletionCallbackWithOutput<OutputT> cc(
&TestCompletionCallback::Handler,
this,
&output_storage_);
if (callback_.callback_type() == PP_OPTIONAL)
cc.set_flags(PP_COMPLETIONCALLBACK_FLAG_OPTIONAL);
return cc;
}
// Verifies that the callback didn't record any errors. If the callback is run
// in an unexpected way (e.g., if it's invoked asynchronously when the call
// should have blocked), this returns an appropriate error string.
#define CHECK_CALLBACK_BEHAVIOR(callback) \
do { \
if ((callback).failed()) \
return MakeFailureMessage(__FILE__, __LINE__, \
(callback).errors().c_str()); \
} while (false)
/*
* A set of macros to use for platform detection. These were largely copied
* from chromium's build_config.h.
*/
#if defined(__APPLE__)
#define PPAPI_OS_MACOSX 1
#elif defined(ANDROID)
#define PPAPI_OS_ANDROID 1
#elif defined(__native_client__)
#define PPAPI_OS_NACL 1
#elif defined(__linux__)
#define PPAPI_OS_LINUX 1
#elif defined(_WIN32)
#define PPAPI_OS_WIN 1
#elif defined(__FreeBSD__)
#define PPAPI_OS_FREEBSD 1
#elif defined(__OpenBSD__)
#define PPAPI_OS_OPENBSD 1
#elif defined(__sun)
#define PPAPI_OS_SOLARIS 1
#else
#error Please add support for your platform in ppapi/tests/test_utils.h
#endif
/* These are used to determine POSIX-like implementations vs Windows. */
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \
defined(__OpenBSD__) || defined(__sun) || defined(__native_client__)
#define PPAPI_POSIX 1
#endif
// By default, ArrayBuffers over a certain size are sent via shared memory. In
// order to test for this without sending huge buffers, tests can use this
// class to set the minimum array buffer size used for shared memory temporarily
// lower.
class ScopedArrayBufferSizeSetter {
public:
ScopedArrayBufferSizeSetter(const PPB_Testing_Private* interface,
PP_Instance instance,
uint32_t threshold)
: interface_(interface),
instance_(instance) {
interface_->SetMinimumArrayBufferSizeForShmem(instance_, threshold);
}
~ScopedArrayBufferSizeSetter() {
interface_->SetMinimumArrayBufferSizeForShmem(instance_, 0);
}
private:
const PPB_Testing_Private* interface_;
PP_Instance instance_;
};
// Opens |request| in |loader| and returns the results of the URLRequest. The
// caller may provide the optional |response_body| argument to get the contents
// of the body of the response to the URLRequest.
//
// Returns PP_OK upon success.
int32_t OpenURLRequest(PP_Instance instance,
pp::URLLoader* loader,
const pp::URLRequestInfo& request,
CallbackType callback_type,
std::string* response_body);
#endif // PPAPI_TESTS_TEST_UTILS_H_