Commit bee7a793 authored by DHNishi@gmail.com's avatar DHNishi@gmail.com

Add optional schema compiler error messages and unit tests.

Provides the ability to generate error messages within schema-compiled code for ease of debugging. 
Error messages may be disabled by adding a 'generate_error_messages' property to json schema, e.g. "generate_error_messages": false

Error generation mostly written by Aaron Jacobs (https://codereview.chromium.org/16462004/).

BUG=234834

Review URL: https://chromiumcodereview.appspot.com/22228002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@217118 0039d316-1c4b-4281-b951-d872f2087c98
parent a176567b
...@@ -1947,6 +1947,7 @@ ...@@ -1947,6 +1947,7 @@
'../tools/json_schema_compiler/test/idl_schemas_unittest.cc', '../tools/json_schema_compiler/test/idl_schemas_unittest.cc',
'../tools/json_schema_compiler/test/objects_unittest.cc', '../tools/json_schema_compiler/test/objects_unittest.cc',
'../tools/json_schema_compiler/test/simple_api_unittest.cc', '../tools/json_schema_compiler/test/simple_api_unittest.cc',
'../tools/json_schema_compiler/test/error_generation_unittest.cc',
'../ui/views/test/desktop_test_views_delegate.cc', '../ui/views/test/desktop_test_views_delegate.cc',
'../ui/views/test/desktop_test_views_delegate.h', '../ui/views/test/desktop_test_views_delegate.h',
'../ui/views/test/test_views_delegate.cc', '../ui/views/test/test_views_delegate.cc',
......
This diff is collapsed.
...@@ -49,9 +49,9 @@ class Code(object): ...@@ -49,9 +49,9 @@ class Code(object):
if line.substitute: if line.substitute:
line.value %= () line.value %= ()
except TypeError: except TypeError:
raise TypeError('Unsubstituted value when concatting\n' + line) raise TypeError('Unsubstituted value when concatting\n' + line.value)
except ValueError: except ValueError:
raise ValueError('Stray % character when concatting\n' + line) raise ValueError('Stray % character when concatting\n' + line.value)
self.Append(line.value, line.substitute) self.Append(line.value, line.substitute)
return self return self
......
...@@ -26,6 +26,8 @@ class _Generator(object): ...@@ -26,6 +26,8 @@ class _Generator(object):
self._cpp_namespace = cpp_namespace self._cpp_namespace = cpp_namespace
self._target_namespace = ( self._target_namespace = (
self._type_helper.GetCppNamespaceName(self._namespace)) self._type_helper.GetCppNamespaceName(self._namespace))
self._generate_error_messages = namespace.compiler_options.get(
'generate_error_messages', False)
def Generate(self): def Generate(self):
"""Generates a Code object with the .h for a single namespace. """Generates a Code object with the .h for a single namespace.
...@@ -228,15 +230,15 @@ class _Generator(object): ...@@ -228,15 +230,15 @@ class _Generator(object):
(c.Append() (c.Append()
.Comment('Populates a %s object from a base::Value. Returns' .Comment('Populates a %s object from a base::Value. Returns'
' whether |out| was successfully populated.' % classname) ' whether |out| was successfully populated.' % classname)
.Append('static bool Populate(const base::Value& value, ' .Append('static bool Populate(%s);' % self._GenerateParams(
'%(classname)s* out);') ('const base::Value& value', '%s* out' % classname)))
) )
if is_toplevel: if is_toplevel:
(c.Append() (c.Append()
.Comment('Creates a %s object from a base::Value, or NULL on ' .Comment('Creates a %s object from a base::Value, or NULL on '
'failure.' % classname) 'failure.' % classname)
.Append('static scoped_ptr<%(classname)s> ' .Append('static scoped_ptr<%s> FromValue(%s);' % (
'FromValue(const base::Value& value);') classname, self._GenerateParams(('const base::Value& value',))))
) )
if type_.origin.from_client: if type_.origin.from_client:
value_type = ('base::Value' value_type = ('base::Value'
...@@ -323,7 +325,8 @@ class _Generator(object): ...@@ -323,7 +325,8 @@ class _Generator(object):
c = Code() c = Code()
(c.Sblock('struct Params {') (c.Sblock('struct Params {')
.Append('static scoped_ptr<Params> Create(const base::ListValue& args);') .Append('static scoped_ptr<Params> Create(%s);' % self._GenerateParams(
('const base::ListValue& args',)))
.Append('~Params();') .Append('~Params();')
.Append() .Append()
.Cblock(self._GenerateTypes(p.type_ for p in function.params)) .Cblock(self._GenerateTypes(p.type_ for p in function.params))
...@@ -385,3 +388,10 @@ class _Generator(object): ...@@ -385,3 +388,10 @@ class _Generator(object):
.Append('} // namespace Results') .Append('} // namespace Results')
) )
return c return c
def _GenerateParams(self, params):
"""Builds the parameter list for a function, given an array of parameters.
"""
if self._generate_error_messages:
params += ('std::string* error = NULL',)
return ', '.join(str(p) for p in params)
...@@ -50,7 +50,7 @@ class Namespace(object): ...@@ -50,7 +50,7 @@ class Namespace(object):
- |functions| a map of function names to their model.Function - |functions| a map of function names to their model.Function
- |events| a map of event names to their model.Function - |events| a map of event names to their model.Function
- |properties| a map of property names to their model.Property - |properties| a map of property names to their model.Property
- |compiler_options| the compiler_options dict, only present if - |compiler_options| the compiler_options dict, only not empty if
|include_compiler_options| is True |include_compiler_options| is True
""" """
def __init__(self, json, source_file, include_compiler_options=False): def __init__(self, json, source_file, include_compiler_options=False):
...@@ -71,8 +71,8 @@ class Namespace(object): ...@@ -71,8 +71,8 @@ class Namespace(object):
self.functions = _GetFunctions(self, json, self) self.functions = _GetFunctions(self, json, self)
self.events = _GetEvents(self, json, self) self.events = _GetEvents(self, json, self)
self.properties = _GetProperties(self, json, self, toplevel_origin) self.properties = _GetProperties(self, json, self, toplevel_origin)
if include_compiler_options: self.compiler_options = (json.get('compiler_options', {})
self.compiler_options = json.get('compiler_options', {}) if include_compiler_options else {})
class Origin(object): class Origin(object):
"""Stores the possible origin of model object as a pair of bools. These are: """Stores the possible origin of model object as a pair of bools. These are:
......
[
{
"namespace": "error_generation",
"description": "Generates ALL the errors.",
"compiler_options": {
"generate_error_messages": true
},
"types": [
{
"id": "TestType",
"type": "object",
"properties": {
"string": {
"type": "string",
"description": "Some string."
}
}
},
{
"id": "ChoiceType",
"type": "object",
"properties": {
"integers": {
"choices": [
{"type": "array", "items": {"type": "integer", "minimum": 0}},
{"type": "integer"}
]
}
}
},
{
"id": "ObjectType",
"type": "object",
"properties": {
"otherType": {
"$ref": "error_generation.TestType",
"optional": true
}
}
},
{
"id": "Enumeration",
"type": "string",
"enum": ["one", "two", "three"]
},
{
"id": "HasEnumeration",
"type": "object",
"properties": {
"enumeration": {
"$ref": "Enumeration"
}
}
},
{
"id": "BinaryData",
"type": "object",
"properties": {
"data": {
"type" : "binary"
}
}
},
{
"id": "ArrayObject",
"type": "object",
"properties": {
"TheArray": {
"type": "array",
"items": {"type": "string"},
"optional": true,
"description": "Expecting a list?"
}
}
}
],
"functions": [
{
"name": "testString",
"type": "function",
"description": "Takes a string. Or not.",
"parameters": [
{
"name": "str",
"type": "string",
"optional": true
}
]
},
{
"name": "testFunction",
"type": "function",
"description": "Specifies a number of parameters.",
"parameters": [
{
"name": "num",
"type": "integer"
}
]
},
{
"name": "testTypeInObject",
"type": "function",
"description": "Takes a TestType.",
"parameters": [
{
"name": "paramObject",
"type": "object",
"properties": {
"testType": {"$ref": "error_generation.TestType", "optional": true},
"boolean": {"type": "boolean"}
}
}
]
}
]
}
]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/json_schema_compiler/test/error_generation.h"
#include "base/json/json_writer.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/json_schema_compiler/test/test_util.h"
using namespace test::api::error_generation;
using base::FundamentalValue;
using json_schema_compiler::test_util::Dictionary;
using json_schema_compiler::test_util::List;
template <typename T>
std::string GetPopulateError(const base::Value& value) {
std::string error;
T test_type;
T::Populate(value, &test_type, &error);
return error;
}
// GenerateTypePopulate errors
TEST(JsonSchemaCompilerErrorTest, RequiredPropertyPopulate) {
{
scoped_ptr<DictionaryValue> value = Dictionary(
"string", new StringValue("bling"));
EXPECT_EQ("", GetPopulateError<TestType>(*value));
}
{
scoped_ptr<base::BinaryValue> value(new base::BinaryValue());
EXPECT_EQ("expected dictionary, got binary",
GetPopulateError<TestType>(*value));
}
}
TEST(JsonSchemaCompilerErrorTest, UnexpectedTypePopulation) {
{
scoped_ptr<base::ListValue> value(new base::ListValue());
EXPECT_EQ("", GetPopulateError<ChoiceType::Integers>(*value));
}
{
scoped_ptr<base::BinaryValue> value(new base::BinaryValue());
EXPECT_EQ("expected integers or integer, got binary",
GetPopulateError<ChoiceType::Integers>(*value));
}
}
// GenerateTypePopulateProperty errors
TEST(JsonSchemaCompilerErrorTest, TypeIsRequired) {
{
scoped_ptr<DictionaryValue> value = Dictionary(
"integers", new FundamentalValue(5));
EXPECT_EQ("", GetPopulateError<ChoiceType>(*value));
}
{
scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
EXPECT_EQ("'integers' is required",
GetPopulateError<ChoiceType>(*value));
}
}
// GenerateParamsCheck errors
TEST(JsonSchemaCompilerErrorTest, TooManyParameters) {
{
scoped_ptr<base::ListValue> params_value = List(
new FundamentalValue(5));
EXPECT_TRUE(TestFunction::Params::Create(*params_value));
}
{
scoped_ptr<base::ListValue> params_value = List(
new FundamentalValue(5),
new FundamentalValue(5));
std::string error;
EXPECT_FALSE(TestFunction::Params::Create(*params_value, &error));
EXPECT_EQ("expected 1 arguments, got 2", error);
}
}
// GenerateFunctionParamsCreate errors
TEST(JsonSchemaCompilerErrorTest, ParamIsRequired) {
{
scoped_ptr<base::ListValue> params_value = List(
new FundamentalValue(5));
EXPECT_TRUE(TestFunction::Params::Create(*params_value));
}
{
scoped_ptr<base::ListValue> params_value = List(
Value::CreateNullValue());
std::string error;
EXPECT_FALSE(TestFunction::Params::Create(*params_value, &error));
EXPECT_EQ("'num' is required", error);
}
}
// GeneratePopulateVariableFromValue errors
TEST(JsonSchemaCompilerErrorTest, WrongPropertyValueType) {
{
scoped_ptr<DictionaryValue> value = Dictionary(
"string", new StringValue("yes"));
EXPECT_EQ("", GetPopulateError<TestType>(*value));
}
{
scoped_ptr<DictionaryValue> value = Dictionary(
"string", new FundamentalValue(1.1));
EXPECT_EQ("'string': expected string, got number",
GetPopulateError<TestType>(*value));
}
}
TEST(JsonSchemaCompilerErrorTest, WrongParameterCreationType) {
{
scoped_ptr<base::ListValue> params_value = List(
new StringValue("Yeah!"));
EXPECT_TRUE(TestString::Params::Create(*params_value));
}
{
scoped_ptr<base::ListValue> params_value = List(
new FundamentalValue(5));
std::string error;
EXPECT_FALSE(TestTypeInObject::Params::Create(*params_value, &error));
EXPECT_EQ("'paramObject': expected dictionary, got integer", error);
}
}
TEST(JsonSchemaCompilerErrorTest, WrongTypeValueType) {
{
scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
EXPECT_EQ("", GetPopulateError<ObjectType>(*value));
}
{
scoped_ptr<DictionaryValue> value = Dictionary(
"otherType", new FundamentalValue(1.1));
EXPECT_EQ("'otherType': expected dictionary, got number",
GetPopulateError<ObjectType>(*value));
}
}
TEST(JsonSchemaCompilerErrorTest, UnableToPopulateArray) {
{
scoped_ptr<base::ListValue> params_value = List(
new FundamentalValue(5));
EXPECT_EQ("", GetPopulateError<ChoiceType::Integers>(*params_value));
}
{
scoped_ptr<base::ListValue> params_value = List(
new FundamentalValue(5),
new FundamentalValue(false));
EXPECT_EQ("unable to populate array 'integers'",
GetPopulateError<ChoiceType::Integers>(*params_value));
}
}
TEST(JsonSchemaCompilerErrorTest, BinaryTypeExpected) {
{
scoped_ptr<DictionaryValue> value = Dictionary(
"data", new base::BinaryValue());
EXPECT_EQ("", GetPopulateError<BinaryData>(*value));
}
{
scoped_ptr<DictionaryValue> value = Dictionary(
"data", new FundamentalValue(1.1));
EXPECT_EQ("'data': expected binary, got number",
GetPopulateError<BinaryData>(*value));
}
}
TEST(JsonSchemaCompilerErrorTest, ListExpected) {
{
scoped_ptr<DictionaryValue> value = Dictionary(
"TheArray", new base::ListValue());
EXPECT_EQ("", GetPopulateError<ArrayObject>(*value));
}
{
scoped_ptr<DictionaryValue> value = Dictionary(
"TheArray", new FundamentalValue(5));
EXPECT_EQ("'TheArray': expected list, got integer",
GetPopulateError<ArrayObject>(*value));
}
}
// GenerateStringToEnumConversion errors
TEST(JsonSchemaCompilerErrorTest, BadEnumValue) {
{
scoped_ptr<DictionaryValue> value = Dictionary(
"enumeration", new StringValue("one"));
EXPECT_EQ("", GetPopulateError<HasEnumeration>(*value));
}
{
scoped_ptr<DictionaryValue> value = Dictionary(
"enumeration", new StringValue("bad sauce"));
EXPECT_EQ("'enumeration': expected \"one\" or \"two\" or \"three\", "
"got \"bad sauce\"",
GetPopulateError<HasEnumeration>(*value));
}
}
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
'idl_object_types.idl', 'idl_object_types.idl',
'objects.json', 'objects.json',
'simple_api.json', 'simple_api.json',
'error_generation.json'
], ],
'cc_dir': 'tools/json_schema_compiler/test', 'cc_dir': 'tools/json_schema_compiler/test',
'root_namespace': 'test::api', 'root_namespace': 'test::api',
......
...@@ -70,5 +70,28 @@ void AddItemToList(const linked_ptr<base::DictionaryValue>& from, ...@@ -70,5 +70,28 @@ void AddItemToList(const linked_ptr<base::DictionaryValue>& from,
out->Append(static_cast<base::Value*>(from->DeepCopy())); out->Append(static_cast<base::Value*>(from->DeepCopy()));
} }
std::string ValueTypeToString(Value::Type type) {
switch(type) {
case Value::TYPE_NULL:
return "null";
case Value::TYPE_BOOLEAN:
return "boolean";
case Value::TYPE_INTEGER:
return "integer";
case Value::TYPE_DOUBLE:
return "number";
case Value::TYPE_STRING:
return "string";
case Value::TYPE_BINARY:
return "binary";
case Value::TYPE_DICTIONARY:
return "dictionary";
case Value::TYPE_LIST:
return "list";
}
NOTREACHED();
return "";
}
} // namespace api_util } // namespace api_util
} // namespace extensions } // namespace extensions
...@@ -173,6 +173,8 @@ scoped_ptr<base::Value> CreateValueFromOptionalArray( ...@@ -173,6 +173,8 @@ scoped_ptr<base::Value> CreateValueFromOptionalArray(
return scoped_ptr<base::Value>(); return scoped_ptr<base::Value>();
} }
std::string ValueTypeToString(Value::Type type);
} // namespace util } // namespace util
} // namespace json_schema_compiler } // namespace json_schema_compiler
......
...@@ -77,3 +77,9 @@ class UtilCCHelper(object): ...@@ -77,3 +77,9 @@ class UtilCCHelper(object):
def GetIncludePath(self): def GetIncludePath(self):
return '#include "tools/json_schema_compiler/util.h"' return '#include "tools/json_schema_compiler/util.h"'
def GetValueTypeString(self, value, is_ptr=False):
call = '.GetType()'
if is_ptr:
call = '->GetType()'
return 'json_schema_compiler::util::ValueTypeToString(%s%s)' % (value, call)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment