java_gen: first shot at unit tests
diff --git a/java_gen/codegen.py b/java_gen/codegen.py
index 0765c91..8b8afa1 100644
--- a/java_gen/codegen.py
+++ b/java_gen/codegen.py
@@ -36,6 +36,7 @@
import of_g
from loxi_ir import *
import lang_java
+import test_data
import loxi_utils.loxi_utils as loxi_utils
@@ -43,14 +44,13 @@
def gen_all_java(out, name):
basedir= '%s/openflowj' % of_g.options.install_dir
- srcdir = "%s/src/main/java/" % basedir
print "Outputting to %s" % basedir
if os.path.exists(basedir):
shutil.rmtree(basedir)
os.makedirs(basedir)
copy_prewrite_tree(basedir)
- gen = JavaGenerator(srcdir)
+ gen = JavaGenerator(basedir)
gen.create_of_interfaces()
gen.create_of_classes()
gen.create_of_const_enums()
@@ -67,11 +67,14 @@
self.basedir = basedir
self.java_model = java_model.model
- def render_class(self, clazz, template, **context):
+ def render_class(self, clazz, template, src_dir=None, **context):
+ if not src_dir:
+ src_dir = "src/main/java/"
+
context['class_name'] = clazz.name
context['package'] = clazz.package
- filename = os.path.join(self.basedir, "%s/%s.java" % (clazz.package.replace(".", "/"), clazz.name))
+ filename = os.path.join(self.basedir, src_dir, "%s/%s.java" % (clazz.package.replace(".", "/"), clazz.name))
dirname = os.path.dirname(filename)
if not os.path.exists(dirname):
os.makedirs(dirname)
@@ -107,6 +110,23 @@
template='of_class.java', version=java_class.version, msg=java_class,
impl_class=java_class.name)
+ self.create_unit_test(java_class.unit_test)
+
+ def create_unit_test(self, unit_test):
+ if unit_test.has_test_data:
+ self.render_class(clazz=unit_test,
+ template='unit_test.java', src_dir="src/test/java",
+ version=unit_test.java_class.version,
+ test=unit_test, msg=unit_test.java_class,
+ test_data=unit_test.test_data)
+
+ def create_of_factories(self):
+ factory = self.java_model.of_factory
+ self.render_class(clazz=factory, template="of_factory_interface.java", factory=factory)
+ for factory_class in factory.factory_classes:
+ self.render_class(clazz=factory_class, template="of_factory_class.java", factory=factory_class, model=self.java_model)
+ self.render_class(clazz=java_model.OFGenericClass(package="org.openflow.protocol", name="OFFactories"), template="of_factories.java", versions=self.java_model.versions)
+
def copy_prewrite_tree(basedir):
""" Recursively copy the directory structure from ./java_gen/pre-write
into $basedir"""
diff --git a/java_gen/java_model.py b/java_gen/java_model.py
index 35f334c..ccb118d 100644
--- a/java_gen/java_model.py
+++ b/java_gen/java_model.py
@@ -218,6 +218,12 @@
self.version = version
self.constant_name = self.c_name.upper().replace("OF_", "")
self.package = "org.openflow.protocol.ver%s" % version.of_version
+ self.generated = False
+
+ @property
+ @memoize
+ def unit_test(self):
+ return JavaUnitTest(self)
@property
def name(self):
@@ -441,6 +447,34 @@
return False
return (self.name,) == (other.name,)
+
+#######################################################################
+### Unit Test
+#######################################################################
+
+class JavaUnitTest(object):
+ def __init__(self, java_class):
+ self.java_class = java_class
+ self.data_file_name = "of{version}/{name}.data".format(version=java_class.version.of_version,
+ name=java_class.c_name[3:])
+ @property
+ def package(self):
+ return self.java_class.package
+
+ @property
+ def name(self):
+ return self.java_class.name + "Test"
+
+ @property
+ def has_test_data(self):
+ return test_data.exists(self.data_file_name)
+
+ @property
+ @memoize
+ def test_data(self):
+ return test_data.read(self.data_file_name)
+
+
#######################################################################
### Enums
#######################################################################
diff --git a/java_gen/java_type.py b/java_gen/java_type.py
index 8580f37..8d0d7bb 100644
--- a/java_gen/java_type.py
+++ b/java_gen/java_type.py
@@ -21,19 +21,19 @@
return camel
-ANY = 0xFFFFFFFFFFFFFFFF
+java_primitive_types = set("byte char short int long".split(" "))
+ANY = 0xFFFFFFFFFFFFFFFF
class VersionOp:
def __init__(self, version=ANY, read=None, write=None):
self.version = version
self.read = read
self.write = write
-
+
def __str__(self):
return "[Version: %d, Read: '%s', Write: '%s']" % (self.version, self.read, self.write)
-
class JType(object):
""" Wrapper class to hold C to Java type conversion information """
def __init__(self, pub_type, priv_type=None, size=None, read_op=None, write_op=None):
@@ -98,6 +98,15 @@
else:
return _write_op.replace("$name", str(name)).replace("$version", version.of_version)
+ @property
+ def is_primitive(self):
+ return self.pub_type in java_primitive_types
+
+ @property
+ def is_array(self):
+ return self.pub_type.endswith("[]")
+
+
hello_elem_list = JType("List<OFHelloElement>") \
.op(read='ChannelUtils.readHelloElementList(bb)', write='ChannelUtils.writeHelloElementList(bb)')
u8 = JType('byte', size=1) \
diff --git a/java_gen/templates/unit_test.java b/java_gen/templates/unit_test.java
new file mode 100644
index 0000000..1462465
--- /dev/null
+++ b/java_gen/templates/unit_test.java
@@ -0,0 +1,102 @@
+//:: # Copyright 2013, Big Switch Networks, Inc.
+//:: #
+//:: # LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+//:: # the following special exception:
+//:: #
+//:: # LOXI Exception
+//:: #
+//:: # As a special exception to the terms of the EPL, you may distribute libraries
+//:: # generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+//:: # that copyright and licensing notices generated by LoxiGen are not altered or removed
+//:: # from the LoxiGen Libraries and the notice provided below is (i) included in
+//:: # the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+//:: # documentation for the LoxiGen Libraries, if distributed in binary form.
+//:: #
+//:: # Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+//:: #
+//:: # You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+//:: # a copy of the EPL at:
+//:: #
+//:: # http::: #www.eclipse.org/legal/epl-v10.html
+//:: #
+//:: # Unless required by applicable law or agreed to in writing, software
+//:: # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+//:: # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+//:: # EPL for the specific language governing permissions and limitations
+//:: # under the EPL.
+//::
+//:: from loxi_ir import *
+//:: import itertools
+//:: import of_g
+//:: include('_copyright.java')
+
+//:: include('_autogen.java')
+
+package ${test.package};
+
+//:: include("_imports.java", msg=msg)
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class ${test.name} {
+ //:: var_type = msg.interface.name
+ //:: var_name = msg.interface.variable_name
+ OFFactory factory;
+
+ final static byte[] ${msg.constant_name}_SERIALIZED =
+ new byte[] { ${", ".join("%s0x%x" % (("" if ord(c)<128 else "(byte) "), ord(c)) for c in test_data["binary"] ) } };
+
+ @Before
+ public void setup() {
+ factory = OFFactories.getFactory(OFVersion.${version.constant_version});
+ }
+
+ //:: if "java" in test_data:
+ @Test
+ public void testWrite() {
+ ${var_type}.Builder builder = factory.create${var_type[2:]}Builder();
+ ${test_data["java"]};
+ ${var_type} ${var_name} = builder.getMessage();
+ ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+ ${var_name}.writeTo(bb);
+ byte[] written = new byte[bb.readableBytes()];
+ bb.readBytes(written);
+
+ assertArrayEquals(${msg.constant_name}_SERIALIZED, written);
+ }
+
+ @Test
+ public void testRead() throws Exception {
+ ${var_type}.Builder builder = factory.create${var_type[2:]}Builder();
+ ${test_data["java"]};
+ ${var_type} ${var_name}Built = builder.getMessage();
+
+ ChannelBuffer input = ChannelBuffers.copiedBuffer(${msg.constant_name}_SERIALIZED);
+
+ // FIXME should invoke the overall reader once implemented
+ ${var_type} ${var_name}Read = ${msg.name}.READER.readFrom(input);
+
+ assertEquals(${var_name}Read, ${var_name}Built);
+ }
+ //:: else:
+ // FIXME: No java stanza in test_data for this class. Add for more comprehensive unit testing
+ //:: #endif
+
+ @Test
+ public void testReadWrite() throws Exception {
+ ChannelBuffer input = ChannelBuffers.copiedBuffer(${msg.constant_name}_SERIALIZED);
+
+ // FIXME should invoke the overall reader once implemented
+ ${var_type} ${var_name} = ${msg.name}.READER.readFrom(input);
+
+ // write message again
+ ChannelBuffer bb = ChannelBuffers.dynamicBuffer();
+ ${var_name}.writeTo(bb);
+ byte[] written = new byte[bb.readableBytes()];
+ bb.readBytes(written);
+
+ assertArrayEquals(${msg.constant_name}_SERIALIZED, written);
+ }
+
+}
diff --git a/test_data/__init__.py b/test_data/__init__.py
index f21770b..7a55c11 100644
--- a/test_data/__init__.py
+++ b/test_data/__init__.py
@@ -45,6 +45,9 @@
result.append(dirname + '/' + filename)
return sorted(result)
+def exists(name):
+ return os.path.exists(os.path.join(_test_data_dir, name))
+
def read(name):
"""
Read, parse, and return a test data file
diff --git a/test_data/of10/hello.data b/test_data/of10/hello.data
index 3dc2b44..d29dff9 100644
--- a/test_data/of10/hello.data
+++ b/test_data/of10/hello.data
@@ -7,3 +7,5 @@
-- c
obj = of_hello_new(OF_VERSION_1_0);
of_hello_xid_set(obj, 305419896);
+-- java
+builder.setXid(0x12345678)
diff --git a/test_data/of10/packet_in.data b/test_data/of10/packet_in.data
index 8168e3b..2cd98b0 100644
--- a/test_data/of10/packet_in.data
+++ b/test_data/of10/packet_in.data
@@ -27,3 +27,11 @@
of_packet_in_reason_set(obj, 1);
of_packet_in_total_len_set(obj, 9);
of_packet_in_xid_set(obj, 305419896);
+-- java
+builder
+ .setXid(0x12345678)
+ .setBufferId(0xabcdef01)
+ .setTotalLen(9)
+ .setInPort(OFPort.LOCAL)
+ .setReason(OFPacketInReason.ACTION)
+ .setData(new byte[] { 0x61, 0x62, 0x63 } );