import datetime
import textwrap
import unittest
from stone.frontend.ast import (
AstNamespace,
AstAlias,
AstVoidField,
AstTagRef,
)
from stone.frontend.exception import InvalidSpec
from stone.frontend.frontend import specs_to_ir
from stone.frontend.parser import ParserFactory
from stone.ir import (
Alias,
is_boolean_type,
is_integer_type,
is_void_type,
Nullable,
RedactedBlot,
RedactedHash,
String,
Map
)
class TestStone(unittest.TestCase):
def setUp(self):
self.parser_factory = ParserFactory(debug=False)
def test_namespace_decl(self):
text = textwrap.dedent("""\
namespace files
""")
out = self.parser_factory.get_parser().parse(text)
self.assertIsInstance(out[0], AstNamespace)
self.assertEqual(out[0].name, 'files')
text = textwrap.dedent("""\
namespace files
""")
out = self.parser_factory.get_parser().parse(text)
self.assertIsInstance(out[0], AstNamespace)
self.assertEqual(out[0].name, 'files')
def test_comments(self):
text = textwrap.dedent("""\
# comment at top
namespace files
# another full line comment
alias Rev = String # partial line comment
struct S # comment before INDENT
"Doc"
# inner comment
f1 UInt64 # partial line comment
# f2 UInt64
# trailing comment
struct S2 # struct def following comment
# start with comment
f1 String # end with partial-line comment
# footer comment
""")
out = self.parser_factory.get_parser().parse(text)
self.assertIsInstance(out[0], AstNamespace)
self.assertIsInstance(out[1], AstAlias)
self.assertEqual(out[2].name, 'S')
self.assertEqual(out[3].name, 'S2')
def test_line_continuations(self):
line_continuation_err = 'Line continuation must increment indent by 1.'
text = textwrap.dedent("""\
namespace test
alias U64 = UInt64(
min_value=0, max_value=10)
struct S
val UInt64(
min_value=0,
max_value=10)
val2 UInt64(
# this
# is
# a
min_value=0
# stress
# test
)
route r(
S,
S,
S
)
"Test route."
""")
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
struct S
val UInt64(
# comment to throw it off
min_value=0)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(line_continuation_err, cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
text = textwrap.dedent("""\
namespace test
struct S
val UInt64(
# comment to throw it off
# x2
min_value=0)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(line_continuation_err, cm.exception.msg)
self.assertEqual(cm.exception.lineno, 7)
def test_type_args(self):
text = textwrap.dedent("""\
namespace test
alias T = String(min_length=3)
alias F = Float64(max_value=3.2e1)
alias Numbers = List(UInt64)
""")
out = self.parser_factory.get_parser().parse(text)
self.assertIsInstance(out[1], AstAlias)
self.assertEqual(out[1].name, 'T')
self.assertEqual(out[1].type_ref.name, 'String')
self.assertEqual(out[1].type_ref.args[1]['min_length'], 3)
self.assertIsInstance(out[2], AstAlias)
self.assertEqual(out[2].name, 'F')
self.assertEqual(out[2].type_ref.name, 'Float64')
self.assertEqual(out[2].type_ref.args[1]['max_value'], 3.2e1)
self.assertIsInstance(out[3], AstAlias)
self.assertEqual(out[3].name, 'Numbers')
self.assertEqual(out[3].type_ref.name, 'List')
self.assertEqual(out[3].type_ref.args[0][0].name, 'UInt64')
def test_struct_decl(self):
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
quota UInt64
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'QuotaInfo')
self.assertEqual(out[1].fields[0].name, 'quota')
self.assertEqual(out[1].fields[0].type_ref.name, 'UInt64')
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
"The space quota info for a user."
quota UInt64
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'QuotaInfo')
self.assertEqual(out[1].doc, 'The space quota info for a user.')
self.assertEqual(out[1].fields[0].name, 'quota')
self.assertEqual(out[1].fields[0].type_ref.name, 'UInt64')
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
"The space quota info for a user."
quota UInt64
"The user's total quota allocation (bytes)."
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'QuotaInfo')
self.assertEqual(out[1].doc, 'The space quota info for a user.')
self.assertEqual(out[1].fields[0].name, 'quota')
self.assertEqual(out[1].fields[0].type_ref.name, 'UInt64')
self.assertEqual(out[1].fields[0].doc, "The user's total quota allocation (bytes).")
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
"The space quota info for a user."
quota UInt64
"The user's total quota allocation (bytes)."
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'QuotaInfo')
self.assertEqual(out[1].doc, 'The space quota info for a user.')
self.assertEqual(out[1].fields[0].name, 'quota')
self.assertEqual(out[1].fields[0].type_ref.name, 'UInt64')
self.assertEqual(out[1].fields[0].doc, "The user's total quota allocation (bytes).")
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
"The space quota info for a user."
quota UInt64
"The user's total quota allocation (bytes)."
example default
quota=64000
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'QuotaInfo')
self.assertIn('default', out[1].examples)
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
"The space quota info for a user."
quota UInt64
"The user's total quota allocation (bytes)."
example default
quota=2000000000
example pro
quota=100000000000
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'QuotaInfo')
self.assertIn('default', out[1].examples)
self.assertIn('pro', out[1].examples)
text = textwrap.dedent("""\
namespace test
struct S1
f1 UInt64
struct S2 extends S1
f2 String
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'S1')
self.assertEqual(out[2].name, 'S2')
self.assertEqual(out[2].extends.name, 'S1')
text = textwrap.dedent("""\
namespace ns
struct S
n1 Int32 = -5
n2 Int32 = 5
f1 Float64 = -1.
f2 Float64 = -4.2
f3 Float64 = -5e-3
f4 Float64 = -5.1e-3
f5 Float64 = 1
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'S')
self.assertEqual(out[1].fields[0].name, 'n1')
self.assertTrue(out[1].fields[0].has_default)
self.assertEqual(out[1].fields[0].default, -5)
self.assertEqual(out[1].fields[1].default, 5)
self.assertEqual(out[1].fields[2].default, -1)
self.assertEqual(out[1].fields[3].default, -4.2)
self.assertEqual(out[1].fields[4].default, -5e-3)
self.assertEqual(out[1].fields[5].default, -5.1e-3)
api = specs_to_ir([('test.stone', text)])
self.assertIsInstance(api.namespaces['ns'].data_type_by_name['S'].fields[6].default, float)
text = textwrap.dedent("""\
namespace test
struct S
f1 String
struct S2 extends S?
f2 String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Reference cannot be nullable.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
def test_struct_patch_decl(self):
text = textwrap.dedent("""\
namespace files
patch struct QuotaInfo
quota UInt64
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'QuotaInfo')
self.assertEqual(out[1].fields[0].name, 'quota')
self.assertEqual(out[1].fields[0].type_ref.name, 'UInt64')
text = textwrap.dedent("""\
namespace files
patch struct QuotaInfo
"The space quota info for a user."
quota UInt64
""")
out = self.parser_factory.get_parser().parse(text)
msg, lineno, _ = self.parser_factory.get_parser().errors[0]
self.assertEqual(msg, "Unexpected STRING with value 'The " +
"space quota info for a user.'.")
self.assertEqual(lineno, 3)
text = textwrap.dedent("""\
namespace files
patch struct QuotaInfo
quota UInt64
"The user's total quota allocation (bytes)."
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'QuotaInfo')
self.assertEqual(out[1].fields[0].name, 'quota')
self.assertEqual(out[1].fields[0].type_ref.name, 'UInt64')
self.assertEqual(out[1].fields[0].doc, "The user's total quota allocation (bytes).")
text = textwrap.dedent("""\
namespace files
patch struct QuotaInfo
quota UInt64
"The user's total quota allocation (bytes)."
example default
quota=64000
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'QuotaInfo')
self.assertIn('default', out[1].examples)
text = textwrap.dedent("""\
namespace files
patch struct QuotaInfo
quota UInt64
"The user's total quota allocation (bytes)."
example default
quota=2000000000
example pro
quota=100000000000
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'QuotaInfo')
self.assertIn('default', out[1].examples)
self.assertIn('pro', out[1].examples)
text = textwrap.dedent("""\
namespace ns
patch struct S
n1 Int32 = -5
n2 Int32 = 5
f1 Float64 = -1.
f2 Float64 = -4.2
f3 Float64 = -5e-3
f4 Float64 = -5.1e-3
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'S')
self.assertEqual(out[1].fields[0].name, 'n1')
self.assertTrue(out[1].fields[0].has_default)
self.assertEqual(out[1].fields[0].default, -5)
self.assertEqual(out[1].fields[1].default, 5)
self.assertEqual(out[1].fields[2].default, -1)
self.assertEqual(out[1].fields[3].default, -4.2)
self.assertEqual(out[1].fields[4].default, -5e-3)
self.assertEqual(out[1].fields[5].default, -5.1e-3)
text = textwrap.dedent("""\
namespace test
struct Resource
union
file File
folder Folder
struct File extends Resource
size UInt64
struct Folder extends Resource
icon String
struct Deleted extends Resource
is_folder Boolean
patch struct Resource
union
deleted Deleted
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Unexpected UNION with value 'union'.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 18)
def test_union_decl(self):
text = textwrap.dedent("""\
namespace files
union Role
"The role a user may have in a shared folder."
owner
"Owner of a file."
viewer
"Read only permission."
editor
"Read and write permission."
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'Role')
self.assertEqual(out[1].doc, 'The role a user may have in a shared folder.')
self.assertIsInstance(out[1].fields[0], AstVoidField)
self.assertEqual(out[1].fields[0].name, 'owner')
self.assertIsInstance(out[1].fields[1], AstVoidField)
self.assertEqual(out[1].fields[1].name, 'viewer')
self.assertIsInstance(out[1].fields[2], AstVoidField)
self.assertEqual(out[1].fields[2].name, 'editor')
text = textwrap.dedent("""\
namespace files
union Error
A
"Variant A"
B
"Variant B"
""")
self.parser_factory.get_parser().parse(text)
text = textwrap.dedent("""\
namespace test
union U1
t1 UInt64
union U2 extends U1
t2 String
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'U1')
self.assertEqual(out[2].name, 'U2')
self.assertEqual(out[2].extends.name, 'U1')
def test_union_patch_decl(self):
text = textwrap.dedent("""\
namespace files
patch union Role
owner
"Owner of a file."
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'Role')
self.assertIsInstance(out[1].fields[0], AstVoidField)
self.assertEqual(out[1].fields[0].name, 'owner')
text = textwrap.dedent("""\
namespace files
patch union Role
"The role a user may have in a shared folder."
owner
"Owner of a file."
""")
out = self.parser_factory.get_parser().parse(text)
msg, lineno, _ = self.parser_factory.get_parser().errors[0]
self.assertEqual(msg, "Unexpected STRING with value 'The " +
"role a user may have in a shared folder.'.")
self.assertEqual(lineno, 3)
text = textwrap.dedent("""\
namespace files
patch union Error
A
"Variant A"
""")
self.parser_factory.get_parser().parse(text)
def test_composition(self):
text = textwrap.dedent("""\
namespace files
union UploadMode
add
overwrite
struct Upload
path String
mode UploadMode = add
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[2].name, 'Upload')
self.assertIsInstance(out[2].fields[1].default, AstTagRef)
self.assertEqual(out[2].fields[1].default.tag, 'add')
def test_route_decl(self):
text = textwrap.dedent("""\
namespace users
route GetAccountInfo(Void, Void, Void)
""")
self.parser_factory.get_parser().parse(text)
text = textwrap.dedent("""\
namespace users
struct AccountInfo
email String
route GetAccountInfo(AccountInfo, Void, Void)
"Gets the account info for a user"
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].name, 'AccountInfo')
self.assertEqual(out[2].name, 'GetAccountInfo')
self.assertEqual(out[2].arg_type_ref.name, 'AccountInfo')
self.assertEqual(out[2].result_type_ref.name, 'Void')
self.assertEqual(out[2].error_type_ref.name, 'Void')
text = textwrap.dedent("""\
namespace users
route GetAccountInfo(Void, Void, Void)
"0
1
2
3
"
""")
out = self.parser_factory.get_parser().parse(text)
self.assertEqual(out[1].doc, '0\n\n1\n\n2\n\n3\n')
text = textwrap.dedent("""\
namespace test
route old_route (Void, Void, Void) deprecated
""")
api = specs_to_ir([('test.stone', text)])
r = api.namespaces['test'].route_by_name['old_route']
self.assertIsNotNone(r.deprecated)
self.assertIsNone(r.deprecated.by)
text = textwrap.dedent("""\
namespace test
route old_route (Void, Void, Void) deprecated by new_route
route new_route (Void, Void, Void)
""")
api = specs_to_ir([('test.stone', text)])
r_old = api.namespaces['test'].route_by_name['old_route']
r_new = api.namespaces['test'].route_by_name['new_route']
self.assertIsNotNone(r_old.deprecated)
self.assertEqual(r_old.deprecated.by, r_new)
text = textwrap.dedent("""\
namespace test
route test/old_route (Void, Void, Void) deprecated by test/new_route
route test/new_route (Void, Void, Void)
""")
api = specs_to_ir([('test.stone', text)])
r_old = api.namespaces['test'].route_by_name['test/old_route']
r_new = api.namespaces['test'].route_by_name['test/new_route']
self.assertIsNotNone(r_old.deprecated)
self.assertEqual(r_old.deprecated.by, r_new)
text = textwrap.dedent("""\
namespace test
route old_route (Void, Void, Void) deprecated by unk_route
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Undefined route 'unk_route' at version 1.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 3)
text = textwrap.dedent("""\
namespace test
route old_route (Void, Void, Void) deprecated by S
struct S
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("'S' must be a route.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 3)
text = textwrap.dedent("""\
namespace test
route get_metadata(Void, Void, Void) deprecated by get_metadata:2
route get_metadata:2(Void, Void, Void)
""")
api = specs_to_ir([('test.stone', text)])
routes = api.namespaces['test'].routes_by_name['get_metadata']
route_v1 = routes.at_version[1]
route_v2 = routes.at_version[2]
self.assertEqual(route_v1.name, 'get_metadata')
self.assertEqual(route_v1.version, 1)
self.assertEqual(route_v2.name, 'get_metadata')
self.assertEqual(route_v2.version, 2)
self.assertIsNotNone(route_v1.deprecated)
self.assertEqual(route_v1.deprecated.by, route_v2)
text = textwrap.dedent("""\
namespace test
route get_metadata:beta(Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Unexpected ID with value 'beta'.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 3)
text = textwrap.dedent("""\
namespace test
route get_metadata:1.2(Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Unexpected FLOAT with value 1.2.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 3)
text = textwrap.dedent("""\
namespace test
route get_metadata:0(Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Version number should be a positive integer.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 3)
text = textwrap.dedent("""\
namespace test
route get_metadata:-1(Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Version number should be a positive integer.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 3)
text = textwrap.dedent("""\
namespace test
route get_metadata(Void, Void, Void) deprecated by get_metadata:3
route get_metadata:2(Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Undefined route 'get_metadata' at version 3.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 3)
text = textwrap.dedent("""\
namespace test
route get_metadata:2(Void, Void, Void)
route get_metadata:2(Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Route 'get_metadata' at version 2 already defined (test.stone:3).", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 5)
text = textwrap.dedent("""\
namespace test
route alpha/get_metadata(Void, Void, Void)
route alpha/get_metadata:2(Void, Void, Void)
""")
api = specs_to_ir([('test.stone', text)])
routes = api.namespaces['test'].routes_by_name['alpha/get_metadata']
route_v1 = routes.at_version[1]
route_v2 = routes.at_version[2]
self.assertEqual(route_v1.name_with_version(), 'alpha/get_metadata')
self.assertEqual(route_v2.name_with_version(), 'alpha/get_metadata:2')
def test_alphabetizing(self):
text1 = textwrap.dedent("""\
namespace ns_b
struct z
f UInt64
union x
a
b
struct y
f UInt64
route b(Void, Void, Void)
route a(Void, Void, Void)
route c(Void, Void, Void)
""")
text2 = textwrap.dedent("""\
namespace ns_a
route d (Void, Void, Void)
""")
api = specs_to_ir([('test1.stone', text1), ('test2.stone', text2)])
assert ['ns_a', 'ns_b'] == list(api.namespaces.keys())
ns_b = api.namespaces['ns_b']
assert [dt.name for dt in ns_b.data_types] == ['x', 'y', 'z']
assert [dt.name for dt in ns_b.routes] == ['a', 'b', 'c']
def test_lexing_errors(self):
text = textwrap.dedent("""\
namespace users
%
# testing line numbers
%
struct AccountInfo
email String
""")
parser = self.parser_factory.get_parser()
out = parser.parse(text)
msg, lineno = parser.lexer.errors[0]
self.assertEqual(msg, "Illegal character '%'.")
self.assertEqual(lineno, 4)
msg, lineno = parser.lexer.errors[1]
self.assertEqual(msg, "Illegal character '%'.")
self.assertEqual(lineno, 8)
self.assertEqual(out[1].name, 'AccountInfo')
text = textwrap.dedent("""\
namespace test
struct S
# Indent below is only 3 spaces
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("Indent is not divisible by 4.", cm.exception.msg)
def test_parsing_errors(self):
text = textwrap.dedent("""\
namespace users
strct AccountInfo
email String
""")
parser = self.parser_factory.get_parser()
parser.parse(text)
msg, lineno, _ = parser.errors[0]
self.assertEqual(msg, "Unexpected ID with value 'strct'.")
self.assertEqual(lineno, 4)
text = textwrap.dedent("""\
namespace users
route test_route(Blah, Blah, Blah)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("Symbol 'Blah' is undefined", cm.exception.msg)
def test_name_clash(self):
text = textwrap.dedent("""\
namespace test_namespace_test
struct TestNamespaceTest
str String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("Name of user-defined type 'TestNamespaceTest' conflicts "
"with name of namespace 'test_namespace_test'", cm.exception.msg)
text = textwrap.dedent("""\
namespace test_namespace_test
route test_namespace_test(Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("Name of route 'test_namespace_test' conflicts "
"with name of namespace 'test_namespace_test'", cm.exception.msg)
text = textwrap.dedent("""\
namespace test_namespace_test
alias TestNamespaceTest = String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("Name of alias 'TestNamespaceTest' conflicts "
"with name of namespace 'test_namespace_test'", cm.exception.msg)
text = textwrap.dedent("""\
namespace test_namespace
struct TestStructTest
str String
route test_struct_test(Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("Name of route 'test_struct_test' conflicts "
"with name of user-defined type 'TestStructTest'", cm.exception.msg)
text = textwrap.dedent("""\
namespace test_namespace
alias TestAliasTest = String
route test_alias_test(Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("Name of route 'test_alias_test' conflicts "
"with name of alias 'TestAliasTest'", cm.exception.msg)
def test_docstrings(self):
text = textwrap.dedent("""\
namespace test
# No docstrings at all
struct E
f String
struct S
"Only type doc"
f String
struct T
f String
"Only field doc"
union U
"Only type doc"
f String
union V
f String
"Only field doc"
# Check for inherited doc
struct W extends T
g String
""")
api = specs_to_ir([('test.stone', text)])
E_dt = api.namespaces['test'].data_type_by_name['E']
self.assertFalse(E_dt.has_documented_type_or_fields())
self.assertFalse(E_dt.has_documented_fields())
S_dt = api.namespaces['test'].data_type_by_name['S']
self.assertTrue(S_dt.has_documented_type_or_fields())
self.assertFalse(S_dt.has_documented_fields())
T_dt = api.namespaces['test'].data_type_by_name['T']
self.assertTrue(T_dt.has_documented_type_or_fields())
self.assertTrue(T_dt.has_documented_fields())
U_dt = api.namespaces['test'].data_type_by_name['U']
self.assertTrue(U_dt.has_documented_type_or_fields())
self.assertFalse(U_dt.has_documented_fields())
V_dt = api.namespaces['test'].data_type_by_name['V']
self.assertTrue(V_dt.has_documented_type_or_fields())
self.assertTrue(V_dt.has_documented_fields())
W_dt = api.namespaces['test'].data_type_by_name['W']
self.assertFalse(W_dt.has_documented_type_or_fields())
self.assertFalse(W_dt.has_documented_fields())
self.assertFalse(W_dt.has_documented_type_or_fields(), True)
self.assertFalse(W_dt.has_documented_fields(), True)
def test_alias(self):
text = textwrap.dedent("""\
namespace test
alias R = String
"This is a test
of docstrings"
""")
api = specs_to_ir([('test.stone', text)])
test_ns = api.namespaces['test']
self.assertIsInstance(test_ns.aliases[0], Alias)
self.assertEqual(test_ns.aliases[0].name, 'R')
self.assertIsInstance(test_ns.aliases[0].data_type, String)
self.assertEqual(
test_ns.aliases[0].doc, 'This is a test of docstrings')
text = textwrap.dedent("""\
namespace test
alias R = String(min_length=1)?
""")
api = specs_to_ir([('test.stone', text)])
test_ns = api.namespaces['test']
self.assertIsInstance(test_ns.aliases[0], Alias)
self.assertEqual(test_ns.aliases[0].name, 'R')
self.assertIsInstance(test_ns.aliases[0].data_type, Nullable)
self.assertIsInstance(test_ns.aliases[0].data_type.data_type, String)
text = textwrap.dedent("""\
namespace test
alias T = String
alias R = T
""")
api = specs_to_ir([('test.stone', text)])
test_ns = api.namespaces['test']
self.assertIsInstance(test_ns.alias_by_name['T'], Alias)
self.assertIsInstance(test_ns.alias_by_name['R'], Alias)
self.assertIsInstance(test_ns.alias_by_name['R'].data_type, Alias)
self.assertEqual(test_ns.alias_by_name['R'].data_type.name, 'T')
text = textwrap.dedent("""\
namespace test
alias R = T
alias T = String
""")
api = specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
alias A = String
alias A = UInt64
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("Symbol 'A' already defined (test.stone:3).",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
alias A = B
alias B = C
alias C = A
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("Alias 'C' is part of a cycle.",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
alias T = String(min_length=1)
alias R = T(min_length=1)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('Attributes cannot be specified for instantiated type',
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct S
f String
alias R = S?
""")
api = specs_to_ir([('test.stone', text)])
test_ns = api.namespaces['test']
S_dt = test_ns.data_type_by_name['S']
self.assertIsInstance(test_ns.alias_by_name['R'].data_type, Nullable)
self.assertEqual(test_ns.alias_by_name['R'].data_type.data_type, S_dt)
text = textwrap.dedent("""\
namespace test
struct S
f String
alias R = S(min_length=1)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('Attributes cannot be specified for instantiated type',
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
route test_route(Void, Void, Void)
alias S = test_route
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("A route cannot be referenced here.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 5)
self.assertEqual(cm.exception.path, 'test.stone')
text1 = textwrap.dedent("""\
namespace test1
struct S
f String
""")
text2 = textwrap.dedent("""\
namespace test2
import test1
alias S = test1.S
""")
api = specs_to_ir([('test1.stone', text1), ('test2.stone', text2)])
test1_ns = api.namespaces['test1']
S_dt = test1_ns.data_type_by_name['S']
test2_ns = api.namespaces['test2']
self.assertEqual(test2_ns.alias_by_name['S'].data_type, S_dt)
text1 = textwrap.dedent("""\
namespace test1
alias Z = S
struct S
f1 String
struct T extends Z
f2 String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test1.stone', text1), ('test2.stone', text2)])
self.assertIn('A struct cannot extend an alias. Use the canonical name instead.',
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 8)
text1 = textwrap.dedent("""\
namespace test1
alias Z = S
union S
f1 String
union T extends Z
f2 String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test1.stone', text1), ('test2.stone', text2)])
self.assertIn(
'A union cannot extend an alias. Use the canonical name instead.',
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 8)
def test_struct_semantics(self):
text = textwrap.dedent("""\
namespace test
struct S
option_a
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Struct field 'option_a' cannot have a Void type.",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct A
a UInt64
a String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('already defined', cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct A
a UInt64
struct B extends A
b String
struct C extends B
a String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('already defined in parent', cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union A
a
struct B extends A
b UInt64
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('struct can only extend another struct', cm.exception.msg)
def test_union_semantics(self):
text = textwrap.dedent("""\
namespace test
union_closed A
a UInt64
a String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('already defined', cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union_closed A
a UInt64
union_closed B extends A
b String
union_closed C extends B
a String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('already defined in parent', cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union A
a
b
""")
api = specs_to_ir([('test.stone', text)])
A_dt = api.namespaces['test'].data_type_by_name['A']
self.assertEqual(A_dt.catch_all_field, A_dt._fields_by_name['other'])
self.assertTrue(A_dt._fields_by_name['other'].catch_all)
text = textwrap.dedent("""\
namespace test
union A
a
union_closed B extends A
b
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Union cannot be closed since parent type 'A' is open.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
text = textwrap.dedent("""\
namespace test
union A
other
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Union cannot define an 'other' field because it is reserved as "
"the catch-all field for open unions.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 4)
text = textwrap.dedent("""\
namespace test
struct A
a UInt64
union B extends A
b UInt64
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('union can only extend another union', cm.exception.msg)
def test_map_semantics(self):
text = textwrap.dedent("""\
namespace test
alias M = Map(String, Int32)
""")
api = specs_to_ir([('test.stone', text)])
m_alias = api.namespaces['test'].alias_by_name['M']
self.assertIsInstance(m_alias, Alias)
self.assertIsInstance(m_alias.data_type, Map)
text = textwrap.dedent("""\
namespace test
alias M = Map(String, Map(String, Int32))
""")
api = specs_to_ir([('test.stone', text)])
m_alias = api.namespaces['test'].alias_by_name['M']
self.assertIsInstance(m_alias.data_type.value_data_type, Map)
text = textwrap.dedent("""\
namespace test
alias M = Map()
""")
with self.assertRaises(InvalidSpec):
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
alias M = Map(String)
""")
with self.assertRaises(InvalidSpec):
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
alias M = Map(String, String, String)
""")
with self.assertRaises(InvalidSpec):
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
alias M = Map(Int32, String)
""")
with self.assertRaises(InvalidSpec):
specs_to_ir([('test.stone', text)])
def test_enumerated_subtypes(self):
text = textwrap.dedent("""\
namespace test
struct Resource
union
file File
folder Folder
struct File extends Resource
size UInt64
struct Folder extends Resource
icon String
""")
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
struct Resource
union
file String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('must be a struct', cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct Resource
union
file File
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('Undefined', cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct Resource
union
file File
struct File
size UInt64
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('not a subtype of', cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct Resource
union
file File
file2 File
struct File extends Resource
size UInt64
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn('only be specified once', cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct Resource
union
file File
struct File extends Resource
size UInt64
struct Folder extends Resource
icon String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("missing 'Folder'", cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct Resource
union
file File
file String
struct File extends Resource
size UInt64
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("already defined on", cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct A
union
b B
c String
struct B extends A
"No enumerated subtypes."
struct C extends B
"No enumerated subtypes."
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("cannot be extended", cm.exception.msg)
def unused_enumerated_subtypes_tests(self):
text = textwrap.dedent("""\
namespace test
struct A
union
b B
c String
struct B extends A
union
c C
struct C extends B
d String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("already defined in parent", cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct A
union
b B
c String
struct B extends A
union
b C
struct C extends B
d String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("already defined in parent", cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct A
union
b B
c String
struct B extends A
"No enumerated subtypes."
struct C extends B
union
d D
struct D extends C
e String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn("cannot enumerate subtypes if parent", cm.exception.msg)
def test_nullable(self):
text = textwrap.dedent("""\
namespace test
alias A = String?
alias B = A?
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
'Cannot mark reference to nullable type as nullable.',
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
alias A = String?
struct S
f A?
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
'Cannot mark reference to nullable type as nullable.',
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct S
f String
struct T extends S?
g String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
'Reference cannot be nullable.',
cm.exception.msg)
def test_forward_reference(self):
text = textwrap.dedent("""\
namespace test
route test_route(Void, S, Void)
struct S
f String
""")
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
struct T extends S
g String
struct S
f String
""")
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
route test_route(Void, T, Void)
struct T
s S
struct S
f String
""")
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
struct S
s S?
""")
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
struct S
s U = a
union U
a
""")
api = specs_to_ir([('test.stone', text)])
self.assertTrue(api.namespaces['test'].data_types[0].fields[0].has_default)
self.assertEqual(
api.namespaces['test'].data_types[0].fields[0].default.union_data_type,
api.namespaces['test'].data_types[1])
self.assertEqual(
api.namespaces['test'].data_types[0].fields[0].default.tag_name, 'a')
def test_struct_patch_semantics(self):
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
"The space quota info for a user."
user_id String
"The user associated with the quota."
example default
user_id="1234"
example pro
user_id="1234P"
patch struct QuotaInfo
quota UInt64
"The user's total quota allocation (bytes)."
example default
quota=2000000000
example pro
quota=100000000000
""")
api = specs_to_ir([('test.stone', text)])
quota_info_dt = api.namespaces['files'].data_type_by_name['QuotaInfo']
self.assertEqual(quota_info_dt.fields[1].name, 'quota')
self.assertTrue(is_integer_type(quota_info_dt.fields[1].data_type))
self.assertEqual(quota_info_dt.fields[1].doc, "The user's total quota allocation (bytes).")
self.assertEqual(quota_info_dt.get_examples()['default'].value['quota'], 2000000000)
self.assertEqual(quota_info_dt.get_examples()['pro'].value['quota'], 100000000000)
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
"The space quota info for a user."
user_id String
"The user associated with the quota."
example default
user_id="1234"
example pro
user_id="1234P"
struct QuotaInfoPersonal extends QuotaInfo
"The space quota info for a personal user."
personal_quota UInt64
"The user's personal quota allocation (bytes)."
patch struct QuotaInfo
quota UInt64
"The user's total quota allocation (bytes)."
example default
quota=2000000000
example pro
quota=100000000000
""")
api = specs_to_ir([('test.stone', text)])
quota_info_dt = api.namespaces['files'].data_type_by_name['QuotaInfoPersonal']
self.assertEqual(quota_info_dt.all_fields[1].name, 'quota')
self.assertTrue(is_integer_type(quota_info_dt.all_fields[1].data_type))
self.assertEqual(
quota_info_dt.all_fields[1].doc, "The user's total quota allocation (bytes).")
text = textwrap.dedent("""\
namespace test
struct Resource
union
file File
folder Folder
struct File extends Resource
size UInt64
"The size of the file."
example default
size=5
struct Folder extends Resource
icon String
"The name of the icon."
example default
icon="My Icon"
patch struct Resource
is_public Boolean
"Whether the resource is public."
patch struct File
example default
is_public=true
patch struct Folder
example default
is_public=false
""")
api = specs_to_ir([('test.stone', text)])
resource_dt = api.namespaces['test'].data_type_by_name['Resource']
self.assertEqual(resource_dt.all_fields[0].name, 'is_public')
self.assertTrue(is_boolean_type(resource_dt.all_fields[0].data_type))
self.assertEqual(resource_dt.all_fields[0].doc, "Whether the resource is public.")
file_dt = api.namespaces['test'].data_type_by_name['File']
self.assertEqual(file_dt.all_fields[0].name, 'is_public')
self.assertTrue(is_boolean_type(file_dt.all_fields[0].data_type))
self.assertEqual(file_dt.all_fields[0].doc, "Whether the resource is public.")
self.assertEqual(file_dt.get_examples()['default'].value['is_public'], True)
folder_dt = api.namespaces['test'].data_type_by_name['Folder']
self.assertEqual(folder_dt.all_fields[0].name, 'is_public')
self.assertTrue(is_boolean_type(file_dt.all_fields[0].data_type))
self.assertEqual(folder_dt.all_fields[0].doc, "Whether the resource is public.")
self.assertEqual(folder_dt.get_examples()['default'].value['is_public'], False)
text = textwrap.dedent("""\
namespace test
struct Resource
union
file File
folder Folder
struct File extends Resource
size UInt64
"The size of the file."
example default
size=5
struct Folder extends Resource
icon String
"The name of the icon."
example default
icon="My Icon"
patch struct Resource
is_public Boolean
"Whether the resource is public."
patch struct File
example default
is_public=true
""")
with self.assertRaises(InvalidSpec) as cm:
api = specs_to_ir([('test.stone', text)])
self.assertEqual("Missing field 'is_public' in example.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 14)
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
"The space quota info for a user."
user_id String
"The user associated with the quota."
example default
user_id="1234"
example pro
user_id="1234P"
patch struct QuotaInfoDoesNotExist
quota UInt64
"The user's total quota allocation (bytes)."
""")
with self.assertRaises(InvalidSpec) as cm:
api = specs_to_ir([('test.stone', text)])
self.assertEqual("Patch 'QuotaInfoDoesNotExist' must correspond " +
"to a pre-existing data_type.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 10)
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
"The space quota info for a user."
user_id String
"The user associated with the quota."
example default
user_id="1234"
example pro
user_id="1234P"
patch union QuotaInfo
quota UInt64
"The user's total quota allocation (bytes)."
""")
with self.assertRaises(InvalidSpec) as cm:
api = specs_to_ir([('test.stone', text)])
self.assertEqual("Type mismatch. Patch 'QuotaInfo' corresponds to " +
"pre-existing data_type 'QuotaInfo' (test.stone:2) that has " +
"type other than 'union'.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 10)
text = textwrap.dedent("""\
namespace files
union_closed QuotaInfo
"The space quota info for a user."
user_id String
"The user associated with the quota."
example default
user_id="1234"
example pro
user_id="1234P"
patch union QuotaInfo
quota UInt64
"The user's total quota allocation (bytes)."
""")
with self.assertRaises(InvalidSpec) as cm:
api = specs_to_ir([('test.stone', text)])
self.assertEqual("Type mismatch. Patch 'QuotaInfo' corresponds to " +
"pre-existing data_type 'QuotaInfo' (test.stone:2) that has " +
"type other than 'union_closed'.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 10)
text = textwrap.dedent("""\
namespace files
union QuotaInfo
"The space quota info for a user."
user_id String
"The user associated with the quota."
example default
user_id="1234"
example pro
user_id="1234P"
patch union_closed QuotaInfo
quota UInt64
"The user's total quota allocation (bytes)."
""")
with self.assertRaises(InvalidSpec) as cm:
api = specs_to_ir([('test.stone', text)])
self.assertEqual("Type mismatch. Patch 'QuotaInfo' corresponds to " +
"pre-existing data_type 'QuotaInfo' (test.stone:2) that has " +
"type other than 'union'.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 10)
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
"The space quota info for a user."
user_id String
"The user associated with the quota."
example default
user_id="1234"
example pro
user_id="1234P"
patch struct QuotaInfo
user_id Int32
"The user associated with the quota."
""")
with self.assertRaises(InvalidSpec) as cm:
api = specs_to_ir([('test.stone', text)])
self.assertEqual("Patched field 'user_id' overrides " +
"pre-existing field in 'QuotaInfo' (test.stone:4).", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 11)
text = textwrap.dedent("""\
namespace files
struct QuotaInfo
"The space quota info for a user."
user_id String
"The user associated with the quota."
example default
user_id="1234"
example pro
user_id="1234P"
patch struct QuotaInfo
quota UInt64
"The user associated with the quota."
example doesNotExist
user_id="1234"
""")
with self.assertRaises(InvalidSpec) as cm:
api = specs_to_ir([('test.stone', text)])
self.assertEqual("Example defined in patch 'QuotaInfo' must " +
"correspond to a pre-existing example.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 13)
def test_union_patch_semantics(self):
text = textwrap.dedent("""\
namespace files
union Role
"The role a user may have in a shared folder."
viewer
"Read only permission."
editor
"Read and write permission."
patch union Role
owner
"Owner of a file."
""")
api = specs_to_ir([('test.stone', text)])
role_info_dt = api.namespaces['files'].data_type_by_name['Role']
self.assertEqual(role_info_dt.fields[2].name, 'owner')
self.assertTrue(is_void_type(role_info_dt.fields[2].data_type))
self.assertEqual(role_info_dt.fields[2].doc, "Owner of a file.")
text = textwrap.dedent("""\
namespace files
union Role
"The role a user may have in a shared folder."
viewer
"Read only permission."
editor
"Read and write permission."
union TeamRole extends Role
"The team role a user may have in a shared folder."
admin
"Admin permission."
patch union Role
owner
"Owner of a file."
""")
api = specs_to_ir([('test.stone', text)])
role_info_dt = api.namespaces['files'].data_type_by_name['TeamRole']
self.assertEqual(role_info_dt.all_fields[2].name, 'owner')
self.assertTrue(is_void_type(role_info_dt.all_fields[2].data_type))
self.assertEqual(role_info_dt.all_fields[2].doc, "Owner of a file.")
text = textwrap.dedent("""\
namespace files
union Role
"The role a user may have in a shared folder."
viewer
"Read only permission."
editor
"Read and write permission."
patch union RoleDoesNotExist
owner
"Owner of a file."
""")
with self.assertRaises(InvalidSpec) as cm:
api = specs_to_ir([('test.stone', text)])
self.assertEqual("Patch 'RoleDoesNotExist' must correspond " +
"to a pre-existing data_type.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 8)
text = textwrap.dedent("""\
namespace files
union Role
"The role a user may have in a shared folder."
viewer
"Read only permission."
editor
"Read and write permission."
patch struct Role
owner String
"Owner of a file."
""")
with self.assertRaises(InvalidSpec) as cm:
api = specs_to_ir([('test.stone', text)])
self.assertEqual("Type mismatch. Patch 'Role' corresponds to " +
"pre-existing data_type 'Role' (test.stone:2) that has " +
"type other than 'struct'.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 8)
text = textwrap.dedent("""\
namespace files
union Role
"The role a user may have in a shared folder."
viewer
"Read only permission."
editor
"Read and write permission."
patch union Role
viewer
"Owner of a file."
""")
with self.assertRaises(InvalidSpec) as cm:
api = specs_to_ir([('test.stone', text)])
self.assertEqual("Patched field 'viewer' overrides " +
"pre-existing field in 'Role' (test.stone:4).", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 9)
def test_import(self):
ns1_text = textwrap.dedent("""\
namespace ns1
import ns2
struct S
f ns2.S
""")
ns2_text = textwrap.dedent("""\
namespace ns2
struct S
f String
""")
specs_to_ir([('ns1.stone', ns1_text), ('ns2.stone', ns2_text)])
text = textwrap.dedent("""\
namespace test
import test
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
'Cannot import current namespace.',
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
import missingns
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Namespace 'missingns' is not defined in any spec.",
cm.exception.msg)
ns1_text = textwrap.dedent("""\
namespace ns1
import ns2
struct S extends ns2.T
f String
""")
ns2_text = textwrap.dedent("""\
namespace ns2
struct T
g String
""")
specs_to_ir([('ns1.stone', ns1_text), ('ns2.stone', ns2_text)])
ns1_text = textwrap.dedent("""\
namespace ns1
import ns2
struct S extends ns2.X
f String
""")
ns2_text = textwrap.dedent("""\
namespace ns2
alias X = T
struct T
g String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('ns1.stone', ns1_text), ('ns2.stone', ns2_text)])
self.assertEqual(
'A struct cannot extend an alias. Use the canonical name instead.',
cm.exception.msg)
ns1_text = textwrap.dedent("""\
namespace ns1
import ns2
union V extends ns2.U
b String
""")
ns2_text = textwrap.dedent("""\
namespace ns2
union U
a
""")
specs_to_ir([('ns1.stone', ns1_text), ('ns2.stone', ns2_text)])
ns1_text = textwrap.dedent("""\
namespace ns1
import ns2
struct S
t ns2.T
""")
ns2_text = textwrap.dedent("""\
namespace ns2
import ns1
struct T
s ns1.S
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('ns1.stone', ns1_text), ('ns2.stone', ns2_text)])
self.assertIn(
"Circular import of namespaces 'ns2' and 'ns1' detected.",
cm.exception.msg)
def test_doc_refs(self):
text = textwrap.dedent("""\
namespace test
union U
":field:`a`"
a
b
""")
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
union U
a
":field:`b`"
b
""")
specs_to_ir([('test.stone', text)])
text1 = textwrap.dedent("""\
namespace test1
union U
a
""")
text2 = textwrap.dedent("""\
namespace test2
import test1
union U
":field:`test1.U.a`"
b
""")
specs_to_ir([('test1.stone', text1), ('test2.stone', text2)])
text = textwrap.dedent("""\
namespace test
route test_route(Void, Void, Void)
struct T
"type doc ref :route:`test_route`"
f String
"field doc ref :route:`test_route`"
union U
"type doc ref :route:`test_route`"
f String
"field doc ref :route:`test_route`"
""")
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
route test_route:2(Void, Void, Void)
struct T
"type doc ref :route:`test_route:2`"
f String
"field doc ref :route:`test_route:2`"
union U
"type doc ref :route:`test_route:2`"
f String
"field doc ref :route:`test_route:2`"
""")
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
struct T
"type doc ref :route:`test_route`"
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Unknown doc reference to route 'test_route'.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 4)
self.assertEqual(cm.exception.path, 'test.stone')
text = textwrap.dedent("""\
namespace test
union U
a
struct T
"type doc ref :route:`U`"
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Doc reference to type 'U' is not a route.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 7)
self.assertEqual(cm.exception.path, 'test.stone')
text = textwrap.dedent("""\
namespace test
route test_route:2(Void, Void, Void)
struct T
"type doc ref :route:`test_route:3`"
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Doc reference to route 'test_route' has undefined version 3.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
self.assertEqual(cm.exception.path, 'test.stone')
text = textwrap.dedent("""\
namespace test
route test_route(Void, Void, Void)
struct T
"type doc ref :field:`test_route.g`"
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual("Bad doc reference to field 'g' of route 'test_route'.", cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
self.assertEqual(cm.exception.path, 'test.stone')
text = textwrap.dedent("""\
namespace test
struct T
f String
alias A = T
struct B
"ref to alias field :field:`A.f`."
""")
specs_to_ir([('test.stone', text)])
def test_namespace(self):
ns1_text = textwrap.dedent("""\
namespace ns1
"
This is a docstring for ns1.
"
struct S
f String
""")
ns2_text = textwrap.dedent("""\
namespace ns1
"
This is another docstring for ns1.
"
struct S2
f String
""")
api = specs_to_ir([('ns1.stone', ns1_text), ('ns2.stone', ns2_text)])
self.assertEqual(
api.namespaces['ns1'].doc,
'This is a docstring for ns1.\nThis is another docstring for ns1.\n')
def test_examples(self):
text = textwrap.dedent("""\
namespace test
struct S
f String
example default
f = "A"
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_types[0]
self.assertTrue(s_dt.get_examples()['default'], {'f': 'A'})
text = textwrap.dedent("""\
namespace test
struct S
f String
example default
f = 5
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Bad example for field 'f': integer is not a valid string",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct S
f String
example true
f = "A"
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
struct S
f String
example default
f = "ZZZZZZ3"
example default
f = "ZZZZZZ4"
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Example with label 'default' already defined on line 6.",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct S
f String
example default
f = "ZZZZZZ3"
f = "ZZZZZZ4"
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Example with label 'default' defines field 'f' more than once.",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct S
example default
example other
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_types[0]
self.assertIn('default', s_dt.get_examples())
self.assertIn('other', s_dt.get_examples())
self.assertNotIn('missing', s_dt.get_examples())
text = textwrap.dedent("""\
namespace test
struct S
f String
example default
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Missing field 'f' in example.",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct S
t T
example default
struct T
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Missing field 't' in example.",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct S
f String = "S"
example default
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_types[0]
self.assertEqual(s_dt.get_examples()['default'].value['f'], 'S')
text = textwrap.dedent("""\
namespace test
struct S
f String?
example default
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_types[0]
self.assertEqual(len(s_dt.get_examples()['default'].value), 0)
text = textwrap.dedent("""\
namespace test
struct S
f String?
example default
f = null
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_types[0]
self.assertEqual(len(s_dt.get_examples()['default'].value), 0)
text = textwrap.dedent("""\
namespace test
struct S
f String
example default
f = null
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Bad example for field 'f': null is not a valid string",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct S
t T
example default
t = default
struct T
f String
example default
f = "A"
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'t': {'f': 'A'}})
text = textwrap.dedent("""\
namespace test
struct S
t T?
example default
t = default
struct T
f String
example default
f = "A"
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'t': {'f': 'A'}})
text = textwrap.dedent("""\
namespace test
struct S
t T?
example default
t = null
struct T
f String
example default
f = "A"
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{})
text = textwrap.dedent("""\
namespace test
struct S
t T?
example default
t = special
struct T
f String
r R
example default
f = "A"
r = default
example special
f = "B"
r = other
struct R
g String
example default
g = "D"
example other
g = "C"
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'t': {'f': 'B', 'r': {'g': 'C'}}})
text = textwrap.dedent("""\
namespace test
struct S
t T?
example default
t = missing
struct T
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Reference to example for 'T' with label 'missing' does not exist.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 7)
text = textwrap.dedent("""\
namespace test
struct S
t T
example default
struct T
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Missing field 't' in example.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
text = textwrap.dedent("""\
namespace test
struct S
t T?
example default
t = 34
struct T
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Bad example for field 't': example must reference label of 'T'",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 7)
text = textwrap.dedent("""\
namespace test
struct S
s S?
f String
example default
f = "A"
s = null
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value, {'f': 'A'})
text = textwrap.dedent("""\
namespace test
struct A
a String
struct B extends A
b String
struct C extends B
c String
example default
a = "A"
b = "B"
c = "C"
""")
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
struct A
a String
struct B extends A
b String
struct C extends B
c String
example default
b = "B"
c = "C"
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Missing field 'a' in example.",
cm.exception.msg)
def test_examples_union(self):
text = textwrap.dedent("""\
namespace test
union U
a
example default
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
'Example for union must specify exactly one tag.',
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union U
a String
b String
example default
a = "A"
b = "B"
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
'Example for union must specify exactly one tag.',
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union U
a String
example default
z = "Z"
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Unknown tag 'z' in example.",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union U
a String
example default
a = default
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Bad example for field 'a': reference is not a valid string",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union U
a String
example default
a = null
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Bad example for field 'a': null is not a valid string",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union U
a
example default
a = null
""")
api = specs_to_ir([('test.stone', text)])
u_dt = api.namespaces['test'].data_type_by_name['U']
self.assertEqual(u_dt.get_examples()['default'].value, {'.tag': 'a'})
self.assertEqual(u_dt.get_examples(compact=True)['default'].value, 'a')
text = textwrap.dedent("""\
namespace test
union U
a
b String
c UInt64
example default
b = "A"
""")
api = specs_to_ir([('test.stone', text)])
u_dt = api.namespaces['test'].data_type_by_name['U']
self.assertEqual(u_dt.get_examples()['default'].value,
{'.tag': 'b', 'b': 'A'})
self.assertEqual(u_dt.get_examples()['a'].value, {'.tag': 'a'})
self.assertEqual(u_dt.get_examples(compact=True)['a'].value, 'a')
self.assertNotIn('b', u_dt.get_examples())
text = textwrap.dedent("""\
namespace test
union U
a String
union V extends U
b String
example default
a = "A"
""")
api = specs_to_ir([('test.stone', text)])
v_dt = api.namespaces['test'].data_type_by_name['V']
self.assertEqual(v_dt.get_examples()['default'].value,
{'.tag': 'a', 'a': 'A'})
text = textwrap.dedent("""\
namespace test
union U
a
s S
example default
s = default
example opt
s = opt
struct S
f String
example default
f = "F"
example opt
f = "O"
""")
api = specs_to_ir([('test.stone', text)])
u_dt = api.namespaces['test'].data_type_by_name['U']
self.assertEqual(u_dt.get_examples()['default'].value,
{'.tag': 's', 'f': 'F'})
self.assertEqual(u_dt.get_examples()['opt'].value,
{'.tag': 's', 'f': 'O'})
self.assertEqual(list(u_dt.get_examples()['default'].value.keys())[0],
'.tag')
text = textwrap.dedent("""\
namespace test
union U
a
s S
example default
s = missing
struct S
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Reference to example for 'S' with label 'missing' does not exist.",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct S
u U
example default
u = a
union U
a
b
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'u': {'.tag': 'a'}})
self.assertEqual(s_dt.get_examples(compact=True)['default'].value,
{'u': 'a'})
text = textwrap.dedent("""\
namespace test
struct S
u U
example default
u = default
union U
a
b S2?
example default
b = default
struct S2
f String
example default
f = "F"
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'u': {'.tag': 'b', 'f': 'F'}})
text = textwrap.dedent("""\
namespace test
union U
a
b
struct S
u U = a
example default
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'u': {'.tag': 'a'}})
self.assertEqual(s_dt.get_examples(compact=True)['default'].value,
{'u': 'a'})
text = textwrap.dedent("""\
namespace test
union U
a UInt64
b
struct S
u U = a
example default
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Field 'u' has an invalid default: invalid reference to non-void option 'a'",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union U
a UInt64
b
struct S
u U = c
example default
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Field 'u' has an invalid default: invalid reference to unknown tag 'c'",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union U
a
example default
a = false
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Bad example for field 'a': example of void type must be null",
cm.exception.msg)
def test_examples_text(self):
text = textwrap.dedent("""\
namespace test
struct S
a String
example default
"This is the text for the example.
And I guess it's kind of long."
a = "Hello, World."
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
example = s_dt.get_examples()['default']
self.assertEqual(
example.text,
"This is the text for the example. And I guess it's kind of long.")
text = textwrap.dedent("""\
namespace test
union U
a
b String
example default
"This is the text for the example.
And I guess it's kind of long."
b = "Hi, World."
""")
api = specs_to_ir([('test.stone', text)])
u_dt = api.namespaces['test'].data_type_by_name['U']
example = u_dt.get_examples()['default']
self.assertEqual(
example.text,
"This is the text for the example. And I guess it's kind of long.")
def test_examples_enumerated_subtypes(self):
text = textwrap.dedent("""\
namespace test
struct S
t T
example other
struct T
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Missing field 't' in example.",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct R
union
s S
t T
a String
example default
s = default
t = default
struct S extends R
b String
struct T extends R
c String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Example for struct with enumerated subtypes must only specify one subtype tag.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 9)
text = textwrap.dedent("""\
namespace test
struct R
union
s S
t T
a String
example default
s = 34
struct S extends R
b String
struct T extends R
c String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Example of struct with enumerated subtypes must be a reference "
"to a subtype's example.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 10)
text = textwrap.dedent("""\
namespace test
struct R
union
s S
t T
a String
example default
z = default
struct S extends R
b String
struct T extends R
c String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Unknown subtype tag 'z' in example.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 10)
text = textwrap.dedent("""\
namespace test
struct R
union
s S
t T
a String
example default
s = default
struct S extends R
b String
example default
a = "A"
b = "B"
struct T extends R
c String
""")
api = specs_to_ir([('test.stone', text)])
r_dt = api.namespaces['test'].data_type_by_name['R']
self.assertEqual(r_dt.get_examples()['default'].value,
{'.tag': 's', 'a': 'A', 'b': 'B'})
self.assertEqual(list(r_dt.get_examples()['default'].value.keys())[0],
'.tag')
text = textwrap.dedent("""\
namespace test
struct R
union
s S
t T
a String
example default
s = default
struct S extends R
b String
struct T extends R
c String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Reference to example for 'S' with label 'default' does not exist.",
cm.exception.msg)
def test_examples_list(self):
text = textwrap.dedent("""\
namespace test
struct S
l List(String)
example default
l = "a"
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Bad example for field 'l': string is not a valid list",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct S
l List(String)
example default
l = ["a", "b", "c"]
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'l': ['a', 'b', 'c']})
text = textwrap.dedent("""\
namespace test
struct S
l List(String)?
l2 List(String)?
example default
l = ["a", "b", "c"]
l2 = null
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'l': ['a', 'b', 'c']})
text = textwrap.dedent("""\
namespace test
struct S
l List(String?)
example default
l = ["a", null, "c"]
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'l': ['a', None, 'c']})
text = textwrap.dedent("""\
namespace test
struct S
l List(T)
example default
l = default
struct T
f String
example default
f = "A"
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Bad example for field 'l': reference is not a valid list",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
struct S
l List(T)
l2 List(T)?
l3 List(T)?
example default
l = [default, default]
l2 = [default]
l3 = null
struct T
f String
example default
f = "A"
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'l': [{'f': 'A'}, {'f': 'A'}],
'l2': [{'f': 'A'}]})
text = textwrap.dedent("""\
namespace test
struct S
l List(T?)
example default
l = [default, null]
struct T
f String
example default
f = "A"
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'l': [{'f': 'A'}, None]})
text = textwrap.dedent("""\
namespace test
struct S
l List(List(String))
example default
l = [["a", "b"], [], ["z"]]
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s_dt.get_examples()['default'].value,
{'l': [['a', 'b'], [], ["z"]]})
text = textwrap.dedent("""\
namespace test
struct S
l List(List(String, max_items=1))
example default
l = [["a", "b"], [], ["z"]]
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Bad example for field 'l': list has more than 1 item(s)",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union U
a List(String)
example default
a = "hi"
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Bad example for field 'a': string is not a valid list",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union U
a List(String)
example default
a = ["hello", "world"]
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['U']
self.assertEqual(s_dt.get_examples()['default'].value,
{".tag": "a", 'a': ["hello", "world"]})
text = textwrap.dedent("""\
namespace test
union U
a List(S)
b List(S)?
example default
a = [default, default]
example default_b
b = [default]
struct S
f String
example default
f = "A"
""")
api = specs_to_ir([('test.stone', text)])
s_dt = api.namespaces['test'].data_type_by_name['U']
self.assertEqual(s_dt.get_examples()['default'].value,
{'.tag': 'a', 'a': [{'f': 'A'}, {'f': 'A'}]})
self.assertEqual(s_dt.get_examples()['default_b'].value,
{'.tag': 'b', 'b': [{'f': 'A'}]})
text = textwrap.dedent("""\
namespace test
union U
a List(List(S))
example default
a = [[default]]
struct S
f String
example default
f = "A"
""")
api = specs_to_ir([('test.stone', text)])
u_dt = api.namespaces['test'].data_type_by_name['U']
self.assertEqual(u_dt.get_examples()['default'].value,
{'.tag': 'a', 'a': [[{'f': 'A'}]]})
text = textwrap.dedent("""\
namespace test
union U
a List(List(String))
example default
a = [["hello", "world"]]
""")
api = specs_to_ir([('test.stone', text)])
u_dt = api.namespaces['test'].data_type_by_name['U']
self.assertEqual(u_dt.get_examples()['default'].value,
{'.tag': 'a', 'a': [['hello', 'world']]})
text = textwrap.dedent("""\
namespace test
union U
a List(List(String))
example default
a = 42
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Bad example for field 'a': integer is not a valid list",
cm.exception.msg)
text = textwrap.dedent("""\
namespace test
union U
a List(List(S))
example default
a = [[default, special]]
struct S
a UInt64
example default
a = 42
example special
a = 100
""")
api = specs_to_ir([('test.stone', text)])
u_dt = api.namespaces['test'].data_type_by_name['U']
self.assertEqual(u_dt.get_examples()['default'].value,
{'.tag': 'a', 'a': [[{'a': 42}, {'a': 100}]]})
text = textwrap.dedent("""\
namespace test
union U
a List(List(V))
example default
a = [[default, special, x]]
union V
x
y UInt64
example default
x = null
example special
y = 100
""")
api = specs_to_ir([('test.stone', text)])
u_dt = api.namespaces['test'].data_type_by_name['U']
self.assertEqual(
u_dt.get_examples()['default'].value,
{'.tag': 'a', 'a': [[{'.tag': 'x'}, {'.tag': 'y', 'y': 100}, {'.tag': 'x'}]]})
def test_examples_map(self):
text = textwrap.dedent("""\
namespace test
struct S
m Map(String, Int32)
example default
m = {"one": 1, "two": 2}
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertIsInstance(s.get_examples()['default'].value, dict)
text = textwrap.dedent("""\
namespace test
alias m = Map(String, Int32)
alias mm = Map(String, m)
struct S
arg mm
"hash of hashes"
example default
arg = {"key": {"one": 1}, "another_key" : {"two" : 2, "three": 3}}
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertIsInstance(s.get_examples()['default'].value, dict)
text = textwrap.dedent("""\
namespace test
struct Substruct
m2 Map(String, Int32)
example example_ref
m2 = {"one": 1, "two": 2}
struct S
m Map(String, Substruct)
example default
m = {"key": example_ref, "another_key": example_ref}
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertIsInstance(s.get_examples()['default'].value, dict)
text = textwrap.dedent("""\
namespace test
struct S
m Map(String, String)
example default
m = {"one": 1}
""")
with self.assertRaises(InvalidSpec):
specs_to_ir([('test.stone', text)])
text = textwrap.dedent("""\
namespace test
struct S
m Map(String, Int32)
example default
m = {
"one": 1, "two": 2
}
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertIsInstance(s.get_examples()['default'].value, dict)
text = textwrap.dedent("""\
namespace test
struct S
m Map(String, Int32)
example default
m =
{
"one": 1, "two": 2
}
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertIsInstance(s.get_examples()['default'].value, dict)
text = textwrap.dedent("""\
namespace test
struct S
m Map(String, Int32)
example default
m =
{
"one": 1,
"two": 2
}
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertIsInstance(s.get_examples()['default'].value, dict)
text = textwrap.dedent("""\
namespace test
struct S
m Map(String, Map(String, Int32))
example default
m = {
"one": {
"one": 11,
"two": 12
},
"two": {
"one": 21,
"two": 22
}
}
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertIsInstance(s.get_examples()['default'].value, dict)
def test_name_conflicts(self):
text = textwrap.dedent("""\
namespace test
struct S
f String
struct S
g String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Symbol 'S' already defined (test.stone:3).",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
text = textwrap.dedent("""\
namespace test
struct S
f String
route S (Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Symbol 'S' already defined (test.stone:3).",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
text = textwrap.dedent("""\
namespace test
struct S
f String
union S
g String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Symbol 'S' already defined (test.stone:3).",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
text = textwrap.dedent("""\
namespace test
struct S
f String
alias S = UInt64
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Symbol 'S' already defined (test.stone:3).",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
text1 = textwrap.dedent("""\
namespace test
struct S
f String
""")
text2 = textwrap.dedent("""\
namespace test
struct S
f String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test1.stone', text1), ('test2.stone', text2)])
self.assertEqual(
"Symbol 'S' already defined (test1.stone:3).",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 4)
def test_imported_namespaces(self):
text1 = textwrap.dedent("""\
namespace ns1
struct S1
f1 String
struct S2
f2 String
alias Iso8601 = Timestamp("%Y-%m-%dT%H:%M:%SZ")
""")
text2 = textwrap.dedent("""\
namespace ns2
import ns1
struct S3
f3 String
f4 ns1.Iso8601?
f5 ns1.S1?
example default
f3 = "hello"
f4 = "2015-05-12T15:50:38Z"
route r1(ns1.S1, ns1.S2, S3)
""")
api = specs_to_ir([('ns1.stone', text1), ('ns2.stone', text2)])
self.assertEqual(api.namespaces['ns2'].get_imported_namespaces(),
[api.namespaces['ns1']])
xs = api.namespaces['ns2'].get_route_io_data_types()
xs = sorted(xs, key=lambda x: x.name.lower())
self.assertEqual(len(xs), 3)
ns1 = api.namespaces['ns1']
ns2 = api.namespaces['ns2']
self.assertEqual(xs[0].namespace, ns1)
self.assertEqual(xs[1].namespace, ns1)
s3_dt = ns2.data_type_by_name['S3']
self.assertEqual(s3_dt.fields[2].data_type.data_type.namespace, ns1)
self.assertEqual(xs[2].name, 'S3')
def test_namespace_obj(self):
text = textwrap.dedent("""\
namespace ns1
struct S1
f1 String
struct S2
f2 String
s3 S3
struct S3
f3 String
struct S4
f4 String
alias A = S2
route r(S1, List(S4?)?, A)
""")
api = specs_to_ir([('ns1.stone', text)])
ns1 = api.namespaces['ns1']
self.assertIn('S1', ns1.data_type_by_name)
self.assertIn('S2', ns1.data_type_by_name)
self.assertIn('S3', ns1.data_type_by_name)
self.assertIn('S4', ns1.data_type_by_name)
self.assertEqual(len(ns1.data_types), 4)
self.assertIn('r', ns1.route_by_name)
self.assertEqual(len(ns1.routes), 1)
s1 = ns1.data_type_by_name['S1']
a = ns1.alias_by_name['A']
s3 = ns1.data_type_by_name['S3']
s4 = ns1.data_type_by_name['S4']
route_data_types = ns1.get_route_io_data_types()
self.assertIn(s1, route_data_types)
self.assertIn(a, route_data_types)
self.assertNotIn(s3, route_data_types)
self.assertIn(s4, route_data_types)
def test_whitespace(self):
text = textwrap.dedent("""\
namespace test
struct S
f String
++++
g Int64
++++
example default
f = "hi"
++++++++
g = 3
route r(Void, S, Void)
""").replace('+', ' ')
specs_to_ir([('ns1.stone', text)])
text = textwrap.dedent("""\
namespace test
struct S
f String
++++
g Int64
++++
example default
f = "hi"
++++
++++++
g = 3
route r(Void, S, Void)
""").replace('+', ' ')
specs_to_ir([('ns1.stone', text)])
text = textwrap.dedent("""\
namespace test
# weirdly indented comment
struct S
# weirdly indented comment
f String
g Int64
example default
f = "hi"
# weirdly indented comment
g = 3
route r(Void, S, Void)
""")
specs_to_ir([('ns1.stone', text)])
def test_route_attrs_schema(self):
stone_cfg_text = textwrap.dedent("""\
namespace stone_cfg
struct Route
f1 String
route r(Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('stone_cfg.stone', stone_cfg_text)])
self.assertEqual(
'No routes can be defined in the stone_cfg namespace.',
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
self.assertEqual(cm.exception.path, 'stone_cfg.stone')
stone_cfg_text = textwrap.dedent("""\
namespace stone_cfg
struct Route
f1 String
""")
test_text = textwrap.dedent("""\
namespace test
route r1(Void, Void, Void)
attrs
f1 = 3
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([
('stone_cfg.stone', stone_cfg_text), ('test.stone', test_text)])
self.assertEqual(
'integer is not a valid string',
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 4)
self.assertEqual(cm.exception.path, 'test.stone')
stone_cfg_text = textwrap.dedent("""\
namespace stone_cfg
struct Route
f1 String
""")
test_text = textwrap.dedent("""\
namespace test
route r1(Void, Void, Void)
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([
('stone_cfg.stone', stone_cfg_text), ('test.stone', test_text)])
self.assertEqual(
"Route does not define attr key 'f1'.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 2)
self.assertEqual(cm.exception.path, 'test.stone')
stone_cfg_text = textwrap.dedent("""\
namespace stone_cfg
struct Route
f1 String = "yay"
""")
test_text = textwrap.dedent("""\
namespace test
route r1(Void, Void, Void)
""")
api = specs_to_ir([
('stone_cfg.stone', stone_cfg_text), ('test.stone', test_text)])
ns1 = api.namespaces['test']
self.assertEqual(ns1.route_by_name['r1'].attrs['f1'], 'yay')
stone_cfg_text = textwrap.dedent("""\
namespace stone_cfg
struct Route
f1 String?
""")
test_text = textwrap.dedent("""\
namespace test
route r1(Void, Void, Void)
""")
api = specs_to_ir([
('stone_cfg.stone', stone_cfg_text), ('test.stone', test_text)])
test = api.namespaces['test']
self.assertEqual(test.route_by_name['r1'].attrs['f1'], None)
stone_cfg_text = textwrap.dedent("""\
namespace stone_cfg
struct Route
f1 String?
""")
test_text = textwrap.dedent("""\
namespace test
route r1(Void, Void, Void)
attrs
f2 = 3
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([
('stone_cfg.stone', stone_cfg_text), ('test.stone', test_text)])
self.assertEqual(
"Route attribute 'f2' is not defined in 'stone_cfg.Route'.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 4)
self.assertEqual(cm.exception.path, 'test.stone')
test_text = textwrap.dedent("""\
namespace test
route r1(Void, Void, Void)
attrs
f1 = 3
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', test_text)])
self.assertEqual(
"Route attribute 'f1' is not defined in 'stone_cfg.Route'.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 4)
self.assertEqual(cm.exception.path, 'test.stone')
stone_cfg_text = textwrap.dedent("""\
namespace stone_cfg
struct Route
f1 Boolean
f2 Bytes
f3 Float64
f4 Int64
f5 String
f6 Timestamp("%Y-%m-%dT%H:%M:%SZ")
f7 S
f8 T
f9 S?
f10 T
f11 S?
alias S = String
alias T = String?
""")
test_text = textwrap.dedent("""\
namespace test
route r1(Void, Void, Void)
attrs
f1 = true
f2 = "asdf"
f3 = 3.2
f4 = 10
f5 = "Hello"
f6 = "2015-05-12T15:50:38Z"
f7 = "World"
f8 = "World"
f9 = "World"
""")
api = specs_to_ir([
('stone_cfg.stone', stone_cfg_text), ('test.stone', test_text)])
test = api.namespaces['test']
attrs = test.route_by_name['r1'].attrs
self.assertEqual(attrs['f1'], True)
self.assertEqual(attrs['f2'], b'asdf')
self.assertEqual(attrs['f3'], 3.2)
self.assertEqual(attrs['f4'], 10)
self.assertEqual(attrs['f5'], 'Hello')
self.assertEqual(
attrs['f6'], datetime.datetime(2015, 5, 12, 15, 50, 38))
self.assertEqual(attrs['f7'], 'World')
self.assertEqual(attrs['f8'], 'World')
self.assertEqual(attrs['f9'], 'World')
self.assertEqual(attrs['f10'], None)
self.assertEqual(attrs['f11'], None)
stone_cfg_text = textwrap.dedent("""\
namespace stone_cfg
import test
struct Route
f1 String
""")
test_text = textwrap.dedent("""\
namespace test
route r1(Void, Void, Void)
attrs
f1 = "1"
f1 = "2"
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([
('stone_cfg.stone', stone_cfg_text), ('test.stone', test_text)])
self.assertEqual(
"Attribute 'f1' defined more than once.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
self.assertEqual(cm.exception.path, 'test.stone')
stone_cfg_text = textwrap.dedent("""\
namespace stone_cfg
import test
struct Route
f1 test.U
""")
test_text = textwrap.dedent("""\
namespace test
union U
a
b
route r1(Void, Void, Void)
attrs
f1 = a
""")
specs_to_ir([
('stone_cfg.stone', stone_cfg_text), ('test.stone', test_text)])
stone_cfg_text = textwrap.dedent("""\
namespace stone_cfg
import test
struct Route
f1 test.U
""")
test_text = textwrap.dedent("""\
namespace test
union U
a
b
route r1(Void, Void, Void)
attrs
f1 = 3
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([
('stone_cfg.stone', stone_cfg_text), ('test.stone', test_text)])
self.assertEqual(
"Expected union tag as value.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 9)
self.assertEqual(cm.exception.path, 'test.stone')
stone_cfg_text = textwrap.dedent("""\
namespace stone_cfg
import test
struct Route
f1 test.U
""")
test_text = textwrap.dedent("""\
namespace test
union U
a
b String
route r1(Void, Void, Void)
attrs
f1 = b
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([
('stone_cfg.stone', stone_cfg_text), ('test.stone', test_text)])
self.assertEqual(
"invalid reference to non-void option 'b'",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 9)
self.assertEqual(cm.exception.path, 'test.stone')
def test_inline_type_def(self):
text = textwrap.dedent("""\
namespace test
struct Photo
dimensions Dimensions
"Dimensions for a photo."
struct
height UInt64
"Height of the photo."
width UInt64
"Width of the photo."
example default
height = 5
width = 10
location GpsCoordinates?
struct
latitude Float64
longitude Float64
example default
latitude = 37.23
longitude = 122.2
time_taken Int64
"The timestamp when the photo was taken."
example default
"A typical photo"
dimensions = default
location = default
time_taken = 100
union E
e1
e2 E2
"Test E2."
union
a
b
route r(Void, Photo, E)
""")
specs_to_ir([('ns1.stone', text)])
text = textwrap.dedent("""\
namespace test
struct T
g Int64
struct S
f T
"Dimensions for a photo or video."
struct
a String
b Int64
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('ns1.stone', text)])
self.assertEqual(
"Symbol 'T' already defined (ns1.stone:3).",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 9)
self.assertEqual(cm.exception.path, 'ns1.stone')
def test_annotations(self):
text = textwrap.dedent("""\
namespace test
annotation Broken = NonExistantType()
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Annotation type 'NonExistantType' does not exist",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 3)
text = textwrap.dedent("""\
namespace test
struct ItsAStruct
f String
annotation NonExistant = ItsAStruct()
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"'ItsAStruct' is not an annotation type",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
text = textwrap.dedent("""\
namespace test
struct S
f String
@NonExistant
"Test field with a non-existant tag."
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Annotation 'NonExistant' does not exist.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
text = textwrap.dedent("""\
namespace test
annotation InternalOnly = Omitted("internal")
struct S
f String
@InternalOnly
"Test field with one omitted tag."
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s.all_fields[0].name, 'f')
self.assertEqual(s.all_fields[0].omitted_caller, 'internal')
text = textwrap.dedent("""\
namespace test
annotation InternalOnly = Omitted("internal")
annotation AlphaOnly = Omitted("alpha_only")
struct S
f String
@AlphaOnly
@InternalOnly
"Test field with two omitted tags."
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Omitted caller already set as 'alpha_only'.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 8)
text = textwrap.dedent("""\
namespace test
annotation Deprecated = Deprecated()
struct S
f String
@Deprecated
"Test field with one deprecated tag."
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s.all_fields[0].name, 'f')
self.assertEqual(s.all_fields[0].deprecated, True)
self.assertIn('Field is deprecated.', s.all_fields[0].doc)
text = textwrap.dedent("""\
namespace test
annotation Deprecated = Deprecated()
struct S
f String
@Deprecated
@Deprecated
"Test field with two deprecated tags."
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Deprecated value already set as 'True'.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 7)
text = textwrap.dedent("""\
namespace test
annotation Preview = Preview()
struct S
f String
@Preview
"Test field with one preview tag."
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s.all_fields[0].name, 'f')
self.assertEqual(s.all_fields[0].preview, True)
self.assertIn('Field is in preview mode - do not rely on in production.',
s.all_fields[0].doc)
text = textwrap.dedent("""\
namespace test
annotation Preview = Preview()
struct S
f String
@Preview
@Preview
"Test field with two preview tags."
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Preview value already set as 'True'.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 7)
text = textwrap.dedent("""\
namespace test
annotation Preview = Preview()
annotation Deprecated = Deprecated()
struct S
f String
@Preview
@Deprecated
"Test field with deprecated and preview tags."
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn(
"'Deprecated' and 'Preview' can't both be set.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 8)
text = textwrap.dedent("""\
namespace test
annotation Preview = Preview()
annotation Deprecated = Deprecated()
struct S
f String
@Deprecated
@Preview
"Test field with deprecated and preview tags."
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn(
"'Deprecated' and 'Preview' can't both be set.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 8)
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedBlot("test_regex")
struct S
f String
@FieldRedactor
"Test field with one redacted blot tag."
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s.all_fields[0].name, 'f')
self.assertTrue(isinstance(s.all_fields[0].redactor, RedactedBlot))
self.assertEqual(s.all_fields[0].redactor.regex, "test_regex")
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedBlot("test_regex")
annotation AnotherFieldRedactor = RedactedBlot("another_test_regex")
struct S
f String
@FieldRedactor
@AnotherFieldRedactor
"Test field with two redacted blot tags."
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn(
"Redactor already set as \"RedactedBlot",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 8)
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedHash("test_regex")
struct S
f UInt32
@FieldRedactor
"Test field with one redacted hash tag."
""")
api = specs_to_ir([('test.stone', text)])
s = api.namespaces['test'].data_type_by_name['S']
self.assertEqual(s.all_fields[0].name, 'f')
self.assertTrue(isinstance(s.all_fields[0].redactor, RedactedHash))
self.assertEqual(s.all_fields[0].redactor.regex, "test_regex")
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedHash("test_regex")
annotation AnotherFieldRedactor = RedactedHash("another_test_regex")
struct S
f String
@FieldRedactor
@AnotherFieldRedactor
"Test field with two redacted hash tags."
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn(
"Redactor already set as \"RedactedHash",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 8)
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedBlot("test_regex")
alias TestAlias = UInt32
@FieldRedactor
""")
api = specs_to_ir([('test.stone', text)])
alias = api.namespaces['test'].alias_by_name['TestAlias']
self.assertTrue(isinstance(alias.redactor, RedactedBlot))
self.assertEqual(alias.redactor.regex, "test_regex")
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedHash("test_regex")
alias TestAlias = String
@FieldRedactor
""")
api = specs_to_ir([('test.stone', text)])
alias = api.namespaces['test'].alias_by_name['TestAlias']
self.assertTrue(isinstance(alias.redactor, RedactedHash))
self.assertEqual(alias.redactor.regex, "test_regex")
text = textwrap.dedent("""\
namespace test
annotation Deprecated = Deprecated()
alias TestAlias = String
@Deprecated
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertIn(
"Aliases only support 'Redacted' and custom annotations, not",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 5)
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedHash("test_regex")
struct T
f String
struct S
f T?
@FieldRedactor
"Test field with non-String type."
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Redactors can't be applied to user-defined or void types.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 10)
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedHash("test_regex")
struct T
f String
alias B = T
alias A = B
@FieldRedactor
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Redactors can't be applied to user-defined or void types.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 9)
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedHash("test_regex")
alias B = String
@FieldRedactor
alias A = B
@FieldRedactor
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"A redactor has already been defined for 'A' by 'B'.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 8)
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedHash("test_regex")
alias A = String
struct T
f A
@FieldRedactor
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Redactors can only be applied to alias definitions, not to alias references.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 8)
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedHash("test_regex")
struct S
f String
struct T
f List(S)
@FieldRedactor
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Redactors can't be applied to user-defined or void types.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 9)
text = textwrap.dedent("""\
namespace test
annotation FieldRedactor = RedactedHash("test_regex")
struct S
f String
struct T
f Map(String, S)
@FieldRedactor
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Redactors can't be applied to user-defined or void types.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 9)
def test_custom_annotations(self):
text = textwrap.dedent("""\
namespace test
struct S
f String
annotation_type AT
s S
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Parameter 's' must have a primitive type (possibly nullable).",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 7)
text = textwrap.dedent("""\
namespace test
annotation Deprecated = Deprecated()
annotation_type AT
s String
@Deprecated
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Annotations cannot be applied to parameters of annotation types",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 6)
text = textwrap.dedent("""\
namespace test
annotation_type Deprecated
s String
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Cannot redefine built-in annotation type 'Deprecated'.",
cm.exception.msg)
self.assertEqual(cm.exception.lineno, 3)
text = textwrap.dedent("""\
namespace test
annotation_type Important
owner String
importance String = "very"
annotation VeryImportant = Important("test-team", importance="very")
""")
with self.assertRaises(InvalidSpec) as cm:
specs_to_ir([('test.stone', text)])
self.assertEqual(
"Annotations accept either positional or keyword arguments, not both",
cm.exception.msg,
)
self.assertEqual(cm.exception.lineno, 6)
text = textwrap.dedent("""\
namespace test
annotation_type Important
owner String
importance String = "very"
annotation VeryImportant = Important("test-team")
annotation SortaImportant = Important(owner="test-team", importance="sorta")
alias TestAlias = String
@VeryImportant
struct TestStruct
f String
@SortaImportant
g List(TestAlias)
""")
api = specs_to_ir([('test.stone', text)])
annotation_type = api.namespaces['test'].annotation_type_by_name['Important']
self.assertEqual(len(annotation_type.params), 2)
self.assertEqual(annotation_type.params[0].name, 'owner')
self.assertFalse(annotation_type.params[0].has_default)
self.assertTrue(isinstance(annotation_type.params[0].data_type, String))
self.assertEqual(annotation_type.params[1].name, 'importance')
self.assertTrue(annotation_type.params[1].has_default)
self.assertEqual(annotation_type.params[1].default, 'very')
self.assertTrue(isinstance(annotation_type.params[1].data_type, String))
annotation = api.namespaces['test'].annotation_by_name['VeryImportant']
self.assertTrue(annotation.annotation_type is annotation_type)
self.assertEqual(annotation.kwargs['owner'], 'test-team')
self.assertEqual(annotation.kwargs['importance'], 'very')
self.assertEqual(annotation.args[0], 'test-team')
self.assertEqual(annotation.args[1], 'very')
annotation = api.namespaces['test'].annotation_by_name['SortaImportant']
self.assertTrue(annotation.annotation_type is annotation_type)
self.assertEqual(annotation.kwargs['owner'], 'test-team')
self.assertEqual(annotation.kwargs['importance'], 'sorta')
self.assertEqual(annotation.args[0], 'test-team')
self.assertEqual(annotation.args[1], 'sorta')
alias = api.namespaces['test'].alias_by_name['TestAlias']
self.assertTrue(alias.custom_annotations[0].annotation_type is annotation_type)
struct = api.namespaces['test'].data_type_by_name['TestStruct']
self.assertEqual(struct.fields[0].custom_annotations[0], annotation)
self.assertEqual(struct.recursive_custom_annotations, {
(alias, api.namespaces['test'].annotation_by_name['VeryImportant']),
(struct.fields[0], api.namespaces['test'].annotation_by_name['SortaImportant']),
})
ns2 = textwrap.dedent("""\
namespace testchain
import test
alias TestAliasChain = String
@test.SortaImportant
struct TestStructChain
f test.TestStruct
g List(TestAliasChain)
""")
ns3 = textwrap.dedent("""\
namespace teststruct
import testchain
struct TestStructToStruct
f testchain.TestStructChain
""")
ns4 = textwrap.dedent("""\
namespace testalias
import testchain
struct TestStructToAlias
f testchain.TestAliasChain
""")
api = specs_to_ir([('test.stone', text), ('testchain.stone', ns2),
('teststruct.stone', ns3), ('testalias.stone', ns4)])
struct_namespaces = [ns.name for ns in
api.namespaces['teststruct'].get_imported_namespaces(
consider_annotation_types=True)]
self.assertTrue('test' in struct_namespaces)
alias_namespaces = [ns.name for ns in
api.namespaces['testalias'].get_imported_namespaces(
consider_annotation_types=True)]
self.assertTrue('test' in alias_namespaces)
if __name__ == '__main__':
unittest.main()