source: trunk/tests/TestValidatingParser.py @ 78

Revision 78, 5.7 KB checked in by tim, 9 years ago (diff)

moved tests to tests folder

  • Property svn:executable set to *
Line 
1import YamlTest
2from here import flushLeft
3from test import assertEquals, assertError
4from TestPullParser import mockParser, Loader
5from yaml import load
6
7"""
8Part of the parse/pull experimental code.  This does a schema-driven
9validating parse of YAML documents, but it's built on top of a
10crufty interim solution.
11
12The schema-driven parser requires a pull parser interface.  Ideally
13a pull-parser would be pulling nodes from a YAML document on an
14as-needed basis, and this is the eventual goal.  But, I don't have
15a pull parser yet, so I simulate one by reading in the entire YAML
16document into a Python data structure, then I do a push-based dump
17of the data structure to a mock emitter that stores up a list of
18parser events that a mock parser then serves up to the schema-driven
19parser on an as-needed basis.  Sounds complex, but there's really not
20much code involved.
21
22Nothing fancy is supported yet--just lists, dictionaries, and scalars;
23no aliases, class transformations, multiple docs, etc.  Also, we lose
24the sort order on map keys, so you will notice that all the examples
25have alphabetically sorted keys.
26
27Also, once we go to a true pull parser, we could have more metadata,
28such as line numbers for nodes, attached comments, etc., that can
29help with error reporting and round-tripping issues.
30"""
31
32testCases = """
33-
34    data: |
35        --- foo
36    schema:
37        type: scalar
38-
39    data: |
40        --- foo
41    schema:
42        type: seq
43    error: |
44        Wanted seq, got scalar
45 -
46    data: &list123 |
47        ---
48        - 1
49        - 2
50        - 3
51    schema:
52        type: seq
53        child:
54            type: scalar
55  -
56    data: *list123
57    schema:
58        type: seq
59        max: 2
60        child:
61            type: scalar
62    error: |
63        Seq has max 2 elements
64-
65    data: |
66        ---
67        city: New Orleans
68        state: LA
69        street: Bourbon
70    schema: &StreetCityState
71        type: map
72        items:
73            - name: city
74              value:
75                  type: scalar
76            - name: state
77              value:
78                  type: scalar
79            - name: street
80              value:
81                  type: scalar
82-
83    data: |
84        ---
85        city: New Orleans
86        state: LA
87        where ya got ya shoes: on ya feet, on Bourbon St.
88    schema: *StreetCityState
89    error: |
90        Expected key 'street', got 'where ya got ya shoes'
91-
92    data: |
93        ---
94        banana: yellow
95        carrot: orange
96        people:
97            - fname: al
98              salary: 44
99            - fname: bob
100              salary: 33
101    schema:
102        type: map
103        items:
104            - name: banana
105              value:
106                type: scalar
107            - name: carrot
108              value:
109                type: scalar
110            - name: people
111              value:
112                type: seq
113                child:
114                    type: map
115                    items:
116                        - name: fname
117                          value:
118                            type: scalar
119                        - name: salary
120                          value:
121                            type: scalar
122"""
123
124class ValidatingLoader:
125    def load(self, data, schema):
126        self.simulateParser(data)
127        return self.loadData(schema)
128
129    def loadData(self, schema):
130        typ = self.parser.getType()
131        return self._load(typ, schema)
132
133    def _load(self, typ, schema):
134        if typ != schema['type']:
135            raise Exception("Wanted %s, got %s\n" % (schema['type'], typ))
136        if typ == 'seq':
137            return self._loadSeq(schema)
138        if typ == 'map':
139            return self._loadMap(schema)
140        else:
141            return self.parser.getScalar()
142
143    def _loadSeq(self, schema):
144        results = []
145        cnt = 0
146        max = schema.get('max', None)
147        schema = schema['child']
148        while 1:
149            typ = self.parser.getType()
150            if typ is None:
151                return results
152            else:
153                cnt += 1
154                self.checkMax(cnt, max)
155                results.append(self._load(typ, schema))
156
157    def _loadMap(self, schema):
158        results = {}
159        for item in schema['items']:
160            self.parser.getType()
161            name = self.parser.getScalar()
162            self.checkName(name, item)
163            value = self.loadData(item['value'])
164            results[name] = value
165        self.parser.getType()
166        return results
167
168    def checkMax(self, cnt, max):
169            if max is not None and cnt > max:
170                raise Exception("Seq has max %d elements\n" % max)
171
172    def checkName(self, name, item):
173        if name != item['name']:
174            raise Exception("Expected key '%s', got '%s'\n" % \
175                (item['name'], name))
176
177    def simulateParser(self, data):
178        # This is the huge hack to work around
179        # not having a true pull parser
180        self.parser = mockParser(oldYamlLoad(data))
181
182def testRoundTrip(data, schema):
183    expected = oldYamlLoad(data)
184    obj = ValidatingLoader().load(data, schema)
185    assertEquals(expected, obj)
186
187def oldYamlLoad(data):
188    return load(data).next()
189
190def testOneCase(test):
191    data = test['data']
192    schema = test['schema']
193    if test.has_key('error'):
194        assertError(lambda: testRoundTrip(data, schema),
195            test['error'])
196    else:
197        testRoundTrip(data, schema)
198
199class Test(YamlTest.YamlTest):
200    def testFromYaml(self):
201        for test in load(testCases).next():
202            testOneCase(test)
203
204if __name__ == '__main__':
205    import unittest
206    unittest.main()
Note: See TracBrowser for help on using the repository browser.