add a test data file parser

This simple format will be used to store multiple syntaxes of the same message
so that test suites for different languages can reuse them
diff --git a/Makefile b/Makefile
index 7e9c377..efc50dc 100644
--- a/Makefile
+++ b/Makefile
@@ -79,6 +79,7 @@
 
 check:
 	PYTHONPATH=. ./utest/test_parser.py
+	PYTHONPATH=. ./utest/test_test_data.py
 
 check-py: python
 	PYTHONPATH=${LOXI_OUTPUT_DIR}/pyloxi python py_gen/tests/generic_util.py
diff --git a/test_data/__init__.py b/test_data/__init__.py
new file mode 100644
index 0000000..dbea7e0
--- /dev/null
+++ b/test_data/__init__.py
@@ -0,0 +1,82 @@
+#!/usr/bin/python
+# 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.
+
+import os
+
+_test_data_dir = os.path.dirname(os.path.realpath(__file__))
+
+def list_files():
+    """
+    Return a list of the data files in this directory
+
+    These strings are suitable to be passed to read().
+    """
+
+    result = []
+    for dirname, dirnames, filenames in os.walk(_test_data_dir):
+        dirname = os.path.relpath(dirname, _test_data_dir)
+        for filename in filenames:
+            if filename.endswith('.data') and not filename.startswith('.'):
+                result.append(dirname + '/' + filename)
+    return sorted(result)
+
+def read(name):
+    """
+    Read, parse, and return a test data file
+
+    @param name Filename relative to the test_data directory
+    @returns A hash from section to the string contents
+
+    A section named "binary" is treated specially: it's treated
+    as a hex dump and parsed into a binary string.
+    """
+
+    section_lines = {}
+    cur_section = None
+
+    with open(os.path.join(_test_data_dir, name)) as f:
+        for line in f:
+            line = line.strip().partition('#')[0].strip()
+            if line == '':
+                continue
+            elif line.startswith('--'):
+                cur_section = line[2:].strip()
+                if cur_section in section_lines:
+                    raise Exception("section %s already exists in the test data file")
+                section_lines[cur_section] = []
+            elif cur_section:
+                section_lines[cur_section].append(line)
+    data = { section: '\n'.join(lines) for (section, lines) in section_lines.items() }
+
+    # Special case: convert 'binary' section into binary
+    # The string '00 11\n22 33' results in "\x00\x11\x22\x33"
+    if 'binary' in data:
+        hex_strs = data['binary'].split()
+        data['binary'] = ''.join(map(lambda x: chr(int(x, 16)), hex_strs))
+
+    return data
diff --git a/test_data/example.data b/test_data/example.data
new file mode 100644
index 0000000..ea2a0ae
--- /dev/null
+++ b/test_data/example.data
@@ -0,0 +1,17 @@
+# Comment outside section
+# Another comment
+
+# That was a blank line
+This is not a blank line
+-- section1
+abc def
+ghi
+-- section2
+123
+456 # comment
+# comment inside a section
+789
+-- binary
+00 01 02 03 04 05 06 07 # comment
+77 66 55 44 33 22 11 00
+ # comment in binary
diff --git a/utest/test_test_data.py b/utest/test_test_data.py
new file mode 100755
index 0000000..6a4a8ff
--- /dev/null
+++ b/utest/test_test_data.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# 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.
+
+import unittest
+import test_data
+
+class DataFileTests(unittest.TestCase):
+    def test_example(self):
+        self.assertTrue('./example.data' in test_data.list_files())
+        data = test_data.read('example.data')
+        self.assertEquals(sorted(['section1', 'section2', 'binary']), sorted(data.keys()))
+        self.assertEquals('abc def\nghi', data['section1'])
+        self.assertEquals('123\n456\n789', data['section2'])
+        self.assertEquals('\x00\x01\x02\x03\x04\x05\x06\x07\x77\x66\x55\x44\x33\x22\x11\x00',
+                          data['binary'])
+
+    # Just make sure all included data files parse without exceptions
+    def test_all(self):
+        for name in test_data.list_files():
+            test_data.read(name)
+
+if __name__ == '__main__':
+    unittest.main()