#!/usr/bin/env python import unittest from stone.frontend.ast import ( AstExample, AstExampleField, AstExampleRef, ) from stone.ir import ( ApiNamespace, Boolean, Float32, Float64, Int32, Int64, InvalidSpec, List, Map, ParameterError, String, Timestamp, UInt32, UInt64, Void, ) from stone.ir import ( Struct, StructField, Union, UnionField, ) class TestStoneInternal(unittest.TestCase): """ Tests the internal representation of a Stone. """ def test_check_example(self): # # Test string # s = String(min_length=1, max_length=5) s.check_example( AstExampleField( path='test.stone', lineno=1, lexpos=0, name='v', value='hello', )) with self.assertRaises(InvalidSpec) as cm: s.check_example( AstExampleField( path='test.stone', lineno=1, lexpos=0, name='v', value='', )) self.assertIn("'' has fewer than 1 character(s)", cm.exception.msg) # # Test list # l1 = List(String(min_length=1), min_items=1, max_items=3) l1.check_example( AstExampleField( path='test.stone', lineno=1, lexpos=0, name='v', value=['asd'], )) with self.assertRaises(InvalidSpec) as cm: l1.check_example( AstExampleField( path='test.stone', lineno=1, lexpos=0, name='v', value=[], )) self.assertIn("has fewer than 1 item(s)", cm.exception.msg) # # Test list of lists # l1 = List(List(String(min_length=1), min_items=1)) l1.check_example( AstExampleField( path='test.stone', lineno=1, lexpos=0, name='v', value=[['asd']], )) with self.assertRaises(InvalidSpec) as cm: l1.check_example( AstExampleField( path='test.stone', lineno=1, lexpos=0, name='v', value=[[]], )) self.assertIn("has fewer than 1 item(s)", cm.exception.msg) # # Test Map type # m = Map(String(), String()) # valid example m.check_example( AstExampleField( path='test.stone', lineno=1, lexpos=0, name='v', value={"foo": "bar"} ) ) # does not conform to declared type with self.assertRaises(InvalidSpec): m.check_example( AstExampleField( path='test.stone', lineno=1, lexpos=0, name='v', value={1: "bar"} ) ) with self.assertRaises(ParameterError): # errors because only string types can be used as keys Map(Int32(), String()) s = Struct('S', None, None) s.set_attributes( "Docstring", [ StructField('a', UInt64(), 'a field', None), StructField('b', List(String()), 'a field', None), ], ) s._add_example( AstExample( 'test.stone', lineno=1, lexpos=0, label='default', text='Default example', fields={ 'a': AstExampleField( path='test.stone', lineno=2, lexpos=0, name='a', value=132, ), 'b': AstExampleField( path='test.stone', lineno=2, lexpos=0, name='b', value=['a'], ), } )) def test_string(self): s = String(min_length=1, max_length=3) # check correct str s.check('1') # check correct unicode s.check('\u2650') # check bad item with self.assertRaises(ValueError) as cm: s.check(99) self.assertIn('not a valid string', cm.exception.args[0]) # check too many characters with self.assertRaises(ValueError) as cm: s.check('12345') self.assertIn('more than 3 character(s)', cm.exception.args[0]) # check too few characters with self.assertRaises(ValueError) as cm: s.check('') self.assertIn('fewer than 1 character(s)', cm.exception.args[0]) def test_int(self): i = Int32() # check valid Int32 i.check(42) # check number that is too large with self.assertRaises(ValueError) as cm: i.check(2**31) self.assertIn('not within range', cm.exception.args[0]) # check number that is too small with self.assertRaises(ValueError) as cm: i.check(-2**31 - 1) self.assertIn('not within range', cm.exception.args[0]) i = UInt32() # check number that is too large with self.assertRaises(ValueError) as cm: i.check(2**32) self.assertIn('not within range', cm.exception.args[0]) # check number that is too small with self.assertRaises(ValueError) as cm: i.check(-1) self.assertIn('not within range', cm.exception.args[0]) i = Int64() # check number that is too large with self.assertRaises(ValueError) as cm: i.check(2**63) self.assertIn('not within range', cm.exception.args[0]) # check number that is too small with self.assertRaises(ValueError) as cm: i.check(-2**63 - 1) self.assertIn('not within range', cm.exception.args[0]) i = UInt64() # check number that is too large with self.assertRaises(ValueError) as cm: i.check(2**64) self.assertIn('not within range', cm.exception.args[0]) # check number that is too small with self.assertRaises(ValueError) as cm: i.check(-1) self.assertIn('not within range', cm.exception.args[0]) i = Int64(min_value=0, max_value=10) with self.assertRaises(ValueError) as cm: i.check(20) self.assertIn('20 is greater than 10', cm.exception.args[0]) with self.assertRaises(ValueError) as cm: i.check(-5) self.assertIn('-5 is less than 0', cm.exception.args[0]) # check that bad ranges are rejected self.assertRaises(ParameterError, lambda: Int64(min_value=0.1)) self.assertRaises(ParameterError, lambda: Int64(max_value='10')) def test_boolean(self): b = Boolean() # check valid bool b.check(True) # check non-bool with self.assertRaises(ValueError) as cm: b.check('true') self.assertIn('not a valid boolean', cm.exception.args[0]) def test_float(self): f = Float32() # check valid float f.check(3.14) # check non-float with self.assertRaises(ValueError) as cm: f.check('1.1') self.assertIn('not a valid real', cm.exception.args[0]) f = Float64(min_value=0, max_value=100) with self.assertRaises(ValueError) as cm: f.check(101) self.assertIn('is greater than', cm.exception.args[0]) with self.assertRaises(ParameterError) as cm: Float64(min_value=0, max_value=10**330) self.assertIn('too large for a float', cm.exception.args[0]) with self.assertRaises(ParameterError) as cm: Float32(min_value=0, max_value=10**50) self.assertIn('greater than the maximum value', cm.exception.args[0]) # check that bad ranges are rejected self.assertRaises(ParameterError, lambda: Float64(min_value=1j)) self.assertRaises(ParameterError, lambda: Float64(max_value='10')) def test_timestamp(self): t = Timestamp('%a, %d %b %Y %H:%M:%S') # check valid timestamp t.check('Sat, 21 Aug 2010 22:31:20') # check bad timestamp with self.assertRaises(ValueError) as cm: t.check('Sat, 21 Aug 2010') self.assertIn('does not match format', cm.exception.args[0]) def test_struct(self): ns = ApiNamespace('test') quota_info = Struct( 'QuotaInfo', None, ns, ) quota_info.set_attributes( "Information about a user's space quota.", [ StructField('quota', UInt64(), 'Total amount of space.', None), ], ) # add an example that doesn't fit the definition of a struct with self.assertRaises(InvalidSpec) as cm: quota_info._add_example( AstExample( path=None, lineno=None, lexpos=None, label='default', text=None, fields={ 'bad_field': AstExampleField( None, None, None, 'bad_field', 'xyz123')})) self.assertIn('has unknown field', cm.exception.msg) quota_info._add_example( AstExample( path=None, lineno=None, lexpos=None, label='default', text=None, fields={ 'quota': AstExampleField( None, None, None, 'quota', 64000)})) # set null for a required field with self.assertRaises(InvalidSpec) as cm: quota_info._add_example( AstExample( path=None, lineno=None, lexpos=None, label='null', text=None, fields={ 'quota': AstExampleField( None, None, None, 'quota', None)})) self.assertEqual( "Bad example for field 'quota': null is not a valid integer", cm.exception.msg) self.assertTrue(quota_info._has_example('default')) quota_info.nullable = True # test for structs within structs account_info = Struct( 'AccountInfo', None, ns, ) account_info.set_attributes( "Information about an account.", [ StructField('account_id', String(), 'Unique identifier for account.', None), StructField('quota_info', quota_info, 'Quota', None) ], ) account_info._add_example( AstExample( path=None, lineno=None, lexpos=None, label='default', text=None, fields={ 'account_id': AstExampleField( None, None, None, 'account_id', 'xyz123'), 'quota_info': AstExampleField( None, None, None, 'quota_info', AstExampleRef( None, None, None, 'default'))}) ) account_info._compute_examples() # ensure that an example for quota_info is propagated up self.assertIn('quota_info', account_info.get_examples()['default'].value) def test_union(self): ns = ApiNamespace('files') update_parent_rev = Struct( 'UpdateParentRev', None, ns, ) update_parent_rev.set_attributes( "Overwrite existing file if the parent rev matches.", [ StructField('parent_rev', String(), 'The revision to be updated.', None) ], ) update_parent_rev._add_example( AstExample( path=None, lineno=None, lexpos=None, label='default', text=None, fields={ 'parent_rev': AstExampleField( None, None, None, 'parent_rev', 'xyz123')})) # test variants with only tags, as well as those with structs. conflict = Union( 'WriteConflictPolicy', None, ns, True, ) conflict.set_attributes( 'Policy for managing write conflicts.', [ UnionField( 'reject', Void(), 'On a write conflict, reject the new file.', None), UnionField( 'overwrite', Void(), 'On a write conflict, overwrite the existing file.', None), UnionField( 'update_if_matching_parent_rev', update_parent_rev, 'On a write conflict, overwrite the existing file.', None), ], ) conflict._add_example( AstExample( path=None, lineno=None, lexpos=None, label='default', text=None, fields={ 'update_if_matching_parent_rev': AstExampleField( None, None, None, 'update_if_matching_parent_rev', AstExampleRef(None, None, None, 'default'))})) conflict._compute_examples() # test that only null value is returned for an example of a Void type self.assertEqual(conflict.get_examples()['reject'].value, {'.tag': 'reject'}) # test that dict is returned for a tagged struct variant self.assertEqual(conflict.get_examples()['default'].value, {'.tag': 'update_if_matching_parent_rev', 'parent_rev': 'xyz123'}) if __name__ == '__main__': unittest.main()