Bug 976802 - add support for testing fake CameraParameters, r=dhylands

This commit is contained in:
Mike Habicher 2014-02-28 17:51:26 -05:00
parent 6908b035ed
commit 7e59fa5afc
6 changed files with 283 additions and 51 deletions

View File

@ -147,15 +147,18 @@ GonkCameraParameters::Initialize()
rv = GetImpl(CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION, mExposureCompensationMin); rv = GetImpl(CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION, mExposureCompensationMin);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; NS_WARNING("Failed to initialize minimum exposure compensation");
mExposureCompensationMin = 0;
} }
rv = GetImpl(CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP, mExposureCompensationStep); rv = GetImpl(CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP, mExposureCompensationStep);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; NS_WARNING("Failed to initialize exposure compensation step size");
mExposureCompensationStep = 0;
} }
rv = GetListAsArray(CAMERA_PARAM_SUPPORTED_ZOOMRATIOS, mZoomRatios); rv = GetListAsArray(CAMERA_PARAM_SUPPORTED_ZOOMRATIOS, mZoomRatios);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; // zoom is not supported
mZoomRatios.Clear();
} }
mInitialized = true; mInitialized = true;
@ -410,6 +413,11 @@ GonkCameraParameters::SetTranslated(uint32_t aKey, const double& aValue)
switch (aKey) { switch (aKey) {
case CAMERA_PARAM_EXPOSURECOMPENSATION: case CAMERA_PARAM_EXPOSURECOMPENSATION:
if (mExposureCompensationStep == 0) {
DOM_CAMERA_LOGE("Exposure compensation not supported, can't set %f\n", aValue);
return NS_ERROR_NOT_AVAILABLE;
}
/** /**
* Convert from real value to a Gonk index, round * Convert from real value to a Gonk index, round
* to the nearest step; index is 1-based. * to the nearest step; index is 1-based.
@ -422,34 +430,46 @@ GonkCameraParameters::SetTranslated(uint32_t aKey, const double& aValue)
case CAMERA_PARAM_ZOOM: case CAMERA_PARAM_ZOOM:
{ {
if (mZoomRatios.Length() == 0) {
DOM_CAMERA_LOGE("Zoom not supported, can't set %fx\n", aValue);
return NS_ERROR_NOT_AVAILABLE;
}
/** /**
* Convert from a real zoom multipler (e.g. 2.5x) to * Convert from a real zoom multipler (e.g. 2.5x) to
* the index of the nearest supported value. * the index of the nearest supported value.
*/ */
value = aValue * 100.0; value = aValue * 100.0;
// mZoomRatios is sorted, so we can binary search it if (value < mZoomRatios[0]) {
unsigned int bottom = 0; index = 0;
unsigned int top = mZoomRatios.Length() - 1; } else if (value > mZoomRatios.LastElement()) {
unsigned int middle; index = mZoomRatios.Length() - 1;
} else {
// mZoomRatios is sorted, so we can binary search it
int bottom = 0;
int top = mZoomRatios.Length() - 1;
int middle;
while (bottom != top) { while (top >= bottom) {
middle = (top + bottom) / 2; middle = (top + bottom) / 2;
if (value == mZoomRatios[middle]) { if (value == mZoomRatios[middle]) {
// exact match // exact match
break; break;
} }
if (value > mZoomRatios[middle] && value < mZoomRatios[middle + 1]) { if (value > mZoomRatios[middle] && value < mZoomRatios[middle + 1]) {
// the specified zoom value lies in this interval // the specified zoom value lies in this interval
break; break;
} }
if (value > mZoomRatios[middle]) { if (value > mZoomRatios[middle]) {
bottom = middle + 1; bottom = middle + 1;
} else { } else {
top = middle - 1; top = middle - 1;
}
} }
index = middle;
} }
index = middle; DOM_CAMERA_LOGI("Zoom = %fx --> index = %d\n", aValue, index);
} }
return SetImpl(CAMERA_PARAM_ZOOM, index); return SetImpl(CAMERA_PARAM_ZOOM, index);
} }
@ -616,16 +636,19 @@ GonkCameraParameters::GetListAsArray(uint32_t aKey, nsTArray<T>& aArray)
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
} }
if (!p) {
DOM_CAMERA_LOGW("Camera parameter %d not available (value is null)\n", aKey);
return NS_ERROR_NOT_AVAILABLE;
}
if (*p == '\0') {
DOM_CAMERA_LOGW("Camera parameter %d not available (value is empty string)\n", aKey);
return NS_ERROR_NOT_AVAILABLE;
}
aArray.Clear(); aArray.Clear();
// If there is no value available, just return the empty array.
if (!p) {
DOM_CAMERA_LOGI("Camera parameter %d not available (value is null)\n", aKey);
return NS_OK;
}
if (*p == '\0') {
DOM_CAMERA_LOGI("Camera parameter %d not available (value is empty string)\n", aKey);
return NS_OK;
}
const char* comma; const char* comma;
while (p) { while (p) {

View File

@ -56,6 +56,33 @@ TestGonkCameraHardware::TestCase()
return test; return test;
} }
const nsCString
TestGonkCameraHardware::GetExtraParameters()
{
/**
* The contents of this pref are appended to the flattened string of
* parameters stuffed into GonkCameraParameters by the camera library.
* It consists of semicolon-delimited key=value pairs, e.g.
*
* focus-mode=auto;flash-mode=auto;preview-size=1024x768
*
* The unflattening process breaks this string up on semicolon boundaries
* and sets an entry in a hashtable of strings with the token before
* the equals sign as the key, and the token after as the value. Because
* the string is parsed in order, key=value pairs occuring later in the
* string will replace value pairs appearing earlier, making it easy to
* inject fake, testable values into the parameters table.
*
* One constraint of this approach is that neither the key nor the value
* may contain equals signs or semicolons. We don't enforce that here
* so that we can also test correct handling of improperly-formatted values.
*/
const nsCString parameters = Preferences::GetCString("camera.control.test.hardware.gonk.parameters");
DOM_CAMERA_LOGA("TestGonkCameraHardware : extra-parameters '%s'\n",
parameters.get());
return parameters;
}
bool bool
TestGonkCameraHardware::IsTestCaseInternal(const char* aTest, const char* aFile, int aLine) TestGonkCameraHardware::IsTestCaseInternal(const char* aTest, const char* aFile, int aLine)
{ {
@ -174,7 +201,14 @@ TestGonkCameraHardware::PullParameters(GonkCameraParameters& aParams)
return static_cast<nsresult>(TestCaseError(UNKNOWN_ERROR)); return static_cast<nsresult>(TestCaseError(UNKNOWN_ERROR));
} }
return GonkCameraHardware::PullParameters(aParams); String8 s = mCamera->getParameters();
nsCString extra = GetExtraParameters();
if (!extra.IsEmpty()) {
s += ";";
s += extra.get();
}
return aParams.Unflatten(s);
} }
int int

View File

@ -55,6 +55,7 @@ public:
protected: protected:
const nsCString TestCase(); const nsCString TestCase();
const nsCString GetExtraParameters();
bool IsTestCaseInternal(const char* aTest, const char* aFile, int aLine); bool IsTestCaseInternal(const char* aTest, const char* aFile, int aLine);
int TestCaseError(int aDefaultError); int TestCaseError(int aDefaultError);

View File

@ -18,13 +18,41 @@ var CameraTest = (function() {
* 'take-picture-process-failure' will simulate a failure of the * 'take-picture-process-failure' will simulate a failure of the
* asynchronous picture-taking process, even if the initial API call * asynchronous picture-taking process, even if the initial API call
* path seems to have succeeded. * path seems to have succeeded.
*
* If 'camera.control.test.hardware.gonk.parameters' is set, it will cause
* the contents of that string to be appended to the string of parameters
* pulled from the Gonk camera library. This allows tests to inject fake
* settings/capabilities for features not supported by the emulator. These
* parameters are one or more semicolon-delimited key=value pairs, e.g. to
* pretend the emulator supports zoom:
*
* zoom-ratios=100,150,200,300,400;max-zoom=4
*
* This means (of course) that neither the key not the value tokens can
* contain either equals signs or semicolons. The test shim doesn't enforce
* this so that we can test getting junk from the camera library as well.
*/ */
const PREF_TEST_ENABLED = "camera.control.test.enabled"; const PREF_TEST_ENABLED = "camera.control.test.enabled";
const PREF_TEST_HARDWARE = "camera.control.test.hardware"; const PREF_TEST_HARDWARE = "camera.control.test.hardware";
const PREF_TEST_EXTRA_PARAMETERS = "camera.control.test.hardware.gonk.parameters";
var oldTestEnabled; var oldTestEnabled;
var oldTestHw; var oldTestHw;
var testMode; var testMode;
function testHardwareSetFakeParameters(parameters, callback) {
SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_EXTRA_PARAMETERS, parameters]]}, function() {
var setParams = SpecialPowers.getCharPref(PREF_TEST_EXTRA_PARAMETERS);
ise(setParams, parameters, "Extra test parameters '" + setParams + "'");
if (callback) {
callback(setParams);
}
});
}
function testHardwareClearFakeParameters(callback) {
SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_EXTRA_PARAMETERS]]}, callback);
}
function testHardwareSet(test, callback) { function testHardwareSet(test, callback) {
SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_HARDWARE, test]]}, function() { SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_HARDWARE, test]]}, function() {
var setTest = SpecialPowers.getCharPref(PREF_TEST_HARDWARE); var setTest = SpecialPowers.getCharPref(PREF_TEST_HARDWARE);
@ -58,6 +86,8 @@ var CameraTest = (function() {
} catch(e) { } } catch(e) { }
testMode = { testMode = {
set: testHardwareSet, set: testHardwareSet,
setFakeParameters: testHardwareSetFakeParameters,
clearFakeParameters: testHardwareClearFakeParameters,
done: testHardwareDone done: testHardwareDone
}; };
if (callback) { if (callback) {
@ -68,32 +98,40 @@ var CameraTest = (function() {
} }
function testEnd(callback) { function testEnd(callback) {
function allDone(cb) { // A chain of clean-up functions....
function cb2() { function allCleanedUp() {
SimpleTest.finish(); SimpleTest.finish();
if (cb) { if (callback) {
cb(); callback();
}
} }
}
function cleanUpTestEnabled() {
var next = allCleanedUp;
if (oldTestEnabled) { if (oldTestEnabled) {
SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_ENABLED, oldTestEnabled]]}, cb2); SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_ENABLED, oldTestEnabled]]}, next);
} else { } else {
SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_ENABLED]]}, cb2); SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_ENABLED]]}, next);
}
}
function cleanUpTest() {
var next = cleanUpTestEnabled;
if (testMode) {
testMode.done(next);
testMode = null;
} else {
next();
}
}
function cleanUpExtraParameters() {
var next = cleanUpTest;
if (testMode) {
testMode.clearFakeParameters(next);
} else {
next();
} }
} }
if (testMode) { cleanUpExtraParameters();
testMode.done(function() {
allDone(callback);
});
testMode = null;
} else {
allDone(function() {
if (callback) {
callback();
}
});
}
} }
ise(SpecialPowers.sanityCheck(), "foo", "SpecialPowers passed sanity check"); ise(SpecialPowers.sanityCheck(), "foo", "SpecialPowers passed sanity check");

View File

@ -7,3 +7,4 @@ support-files = camera_common.js
[test_camera_hardware_init_failure.html] [test_camera_hardware_init_failure.html]
[test_camera_hardware_failures.html] [test_camera_hardware_failures.html]
[test_bug975472.html] [test_bug975472.html]
[test_camera_fake_parameters.html]

View File

@ -0,0 +1,135 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for CameraParameters we need to fake</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="camera_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=976802">Mozilla Bug 976802</a>
<video id="viewfinder" width="200" height="200" autoplay></video>
<img src="#" alt="This image is going to load" id="testimage"/>
<script class="testbody" type="text/javascript;version=1.7">
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var initialConfig = {
mode: 'picture',
recorderProfile: 'cif',
previewSize: {
width: 352,
height: 288
}
};
var cameraObj = null;
// Shorthand functions
function end() {
CameraTest.end();
}
function next() {
CameraTest.next();
}
function run() {
CameraTest.run();
}
function onError(e) {
ok(false, "Error" + JSON.stringify(e));
}
// The array of tests
var tests = [
{
key: "fake-zoom",
prep: function setupFakeZoom(test) {
test.setFakeParameters("zoom-ratios=100,150,200,300,400;max-zoom=4", function() {
run();
});
},
test: function testFakeZoom(cam, cap) {
ok(cap.zoomRatios.length == 5, "zoom ratios length = " + cap.zoomRatios.length);
// test individual zoom ratios
cap.zoomRatios.forEach(function(zoom, index) {
cam.zoom = zoom;
ok(cam.zoom === zoom,
"zoom[" + index + "] = " + zoom + "x, cam.zoom = " + cam.zoom + "x");
});
// test below-lower-bound zoom ratio
var zoom = cap.zoomRatios[0] - 0.1;
cam.zoom = zoom;
ok(cam.zoom === cap.zoomRatios[0],
zoom + "x zoom clamps to minimum: " +
cap.zoomRatios[0] + "x, cam.zoom = " + cam.zoom + "x");
// test above-upper-bound zoom ratio
zoom = cap.zoomRatios.slice(-1)[0] + 1.0;
cam.zoom = zoom;
ok(cam.zoom === cap.zoomRatios.slice(-1)[0],
zoom + "x zoom clamps to maximum: " + cap.zoomRatios.slice(-1)[0] +
"x, cam.zoom = " + cam.zoom + "x");
// test snapping to supported zoom ratio
if (cap.zoomRatios.length > 1) {
zoom = (cap.zoomRatios[0] + cap.zoomRatios[1]) / 2;
cam.zoom = zoom;
ok(cam.zoom === cap.zoomRatios[0],
zoom + "x zoom rounded down to: " + cap.zoomRatios[0] +
"x, cam.zoom = " + cam.zoom + "x");
}
next();
}
},
];
var testGenerator = function() {
for (var i = 0; i < tests.length; ++i ) {
yield tests[i];
}
}();
window.addEventListener('beforeunload', function() {
document.getElementById('viewfinder').mozSrcObject = null;
cameraObj.release();
cameraObj = null;
});
CameraTest.begin("hardware", function(test) {
function onError(error) {
ok(false, "getCamera() failed with: " + error);
end();
}
CameraTest.next = function() {
try {
var t = testGenerator.next();
info("test: " + t.key);
function onSuccess(camera, config) {
cameraObj = camera;
t.test(camera, camera.capabilities);
}
CameraTest.run = function() {
navigator.mozCameras.getCamera(whichCamera, initialConfig, onSuccess, onError);
};
t.prep(test);
} catch(e) {
if (e instanceof StopIteration) {
end();
} else {
throw e;
}
}
};
next();
});
</script>
</body>
</html>