235 lines
8.2 KiB
Python
235 lines
8.2 KiB
Python
# Copyright 2018 Orange
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""Unit tests for typechecker"""
|
|
|
|
import six
|
|
|
|
from congress import data_types
|
|
from congress.datalog import base as datalog
|
|
from congress.datalog import compile as ast
|
|
from congress.datalog import nonrecursive
|
|
from congress.datalog import ruleset
|
|
from congress.tests import base
|
|
from congress.z3 import typechecker
|
|
|
|
|
|
def mkc(typ, nullable):
|
|
return {'type': typ, 'nullable': nullable}
|
|
|
|
|
|
class TestMinTypes(base.TestCase):
|
|
|
|
def setUp(self):
|
|
try:
|
|
data_types.TypesRegistry.type_class('Enum')
|
|
except KeyError:
|
|
typ = data_types.create_congress_enum_type(
|
|
'Enum', ['a', 'b', 'c'], data_types.Str)
|
|
data_types.TypesRegistry.register(typ)
|
|
super(TestMinTypes, self).setUp()
|
|
|
|
def test_not_convertible(self):
|
|
self.assertIsNone(typechecker.min_type('Str', 'Int', False))
|
|
|
|
def test_convertible(self):
|
|
self.assertEqual('Enum', typechecker.min_type('Str', 'Enum', False))
|
|
self.assertEqual('Enum', typechecker.min_type('Enum', 'Str', False))
|
|
|
|
def test_constrained(self):
|
|
self.assertEqual('Enum', typechecker.min_type('Str', 'Enum', True))
|
|
self.assertIsNone(typechecker.min_type('Enum', 'Str', True))
|
|
|
|
|
|
class TestCellPrimitives(base.TestCase):
|
|
|
|
def test_constrain1(self):
|
|
tc = typechecker.Typechecker([], [])
|
|
cell = mkc(None, False)
|
|
tc.work = False
|
|
tc.constrain_type(cell, 'Str')
|
|
self.assertEqual('Str', cell['type'])
|
|
self.assertIs(True, tc.work)
|
|
|
|
def test_constrain2(self):
|
|
tc = typechecker.Typechecker([], [])
|
|
cell = mkc('Int', False)
|
|
tc.work = False
|
|
tc.constrain_type(cell, 'Str')
|
|
self.assertEqual('Scalar', cell['type'])
|
|
self.assertIs(True, tc.work)
|
|
|
|
def test_constrain3(self):
|
|
tc = typechecker.Typechecker([], [])
|
|
cell = mkc('Str', False)
|
|
tc.work = False
|
|
tc.constrain_type(cell, 'Str')
|
|
self.assertEqual('Str', cell['type'])
|
|
self.assertIs(False, tc.work)
|
|
|
|
def test_nullable1(self):
|
|
tc = typechecker.Typechecker([], [])
|
|
cell = mkc('Str', False)
|
|
tc.work = False
|
|
tc.set_nullable(cell)
|
|
self.assertIs(True, cell['nullable'])
|
|
self.assertIs(True, tc.work)
|
|
|
|
def test_nullable2(self):
|
|
tc = typechecker.Typechecker([], [])
|
|
cell = mkc('Str', True)
|
|
tc.work = False
|
|
tc.set_nullable(cell)
|
|
self.assertIs(True, cell['nullable'])
|
|
self.assertIs(False, tc.work)
|
|
|
|
def test_type_cells1(self):
|
|
tc = typechecker.Typechecker([], [])
|
|
cell1, cell2 = mkc('Str', True), mkc(None, False)
|
|
self.assertIsNone(tc.type_cells(cell1, cell2, False))
|
|
self.assertEqual(mkc('Str', True), cell1)
|
|
self.assertEqual(mkc('Str', True), cell2)
|
|
self.assertIs(True, tc.work)
|
|
|
|
def test_type_cells2(self):
|
|
tc = typechecker.Typechecker([], [])
|
|
cell1, cell2 = mkc(None, False), mkc('Str', True)
|
|
self.assertIsNone(tc.type_cells(cell1, cell2, False))
|
|
self.assertEqual(mkc('Str', True), cell1)
|
|
self.assertEqual(mkc('Str', True), cell2)
|
|
self.assertIs(True, tc.work)
|
|
|
|
def test_type_cells3(self):
|
|
tc = typechecker.Typechecker([], [])
|
|
cell1, cell2 = mkc(None, False), mkc('Str', True)
|
|
self.assertIsNone(tc.type_cells(cell1, cell2, False))
|
|
self.assertEqual(mkc('Str', True), cell1)
|
|
self.assertEqual(mkc('Str', True), cell2)
|
|
self.assertIs(True, tc.work)
|
|
|
|
def test_type_cells4(self):
|
|
tc = typechecker.Typechecker([], [])
|
|
cell1, cell2 = mkc('Str', False), mkc('Str', False)
|
|
self.assertIsNone(tc.type_cells(cell1, cell2, False))
|
|
self.assertEqual(mkc('Str', False), cell1)
|
|
self.assertEqual(mkc('Str', False), cell2)
|
|
self.assertIs(False, tc.work)
|
|
cell1, cell2 = mkc('Str', True), mkc('Str', True)
|
|
self.assertIsNone(tc.type_cells(cell1, cell2, False))
|
|
cell1, cell2 = mkc(None, False), mkc(None, False)
|
|
self.assertIsNone(tc.type_cells(cell1, cell2, False))
|
|
self.assertIs(False, tc.work)
|
|
|
|
def test_type_cells5(self):
|
|
tc = typechecker.Typechecker([], [])
|
|
cell1, cell2 = mkc('Int', False), mkc('Str', True)
|
|
self.assertIsNotNone(tc.type_cells(cell1, cell2, False))
|
|
|
|
def test_type_constant(self):
|
|
tc = typechecker.Typechecker([], [])
|
|
|
|
def check(val, typ, nullable):
|
|
cell = mkc(None, False)
|
|
tc.type_constant(val, cell)
|
|
self.assertEqual(mkc(typ, nullable), cell)
|
|
|
|
check(1, 'Int', False)
|
|
check('aaa', 'Str', False)
|
|
check(True, 'Bool', False)
|
|
check(1.3, 'Float', False)
|
|
check(None, None, True)
|
|
check((1, 3), 'Scalar', False)
|
|
|
|
|
|
class MinTheory(nonrecursive.RuleHandlingMixin, datalog.Theory):
|
|
|
|
def __init__(self, name, theories):
|
|
super(MinTheory, self).__init__(name=name, theories=theories)
|
|
self.rules = ruleset.RuleSet()
|
|
self.schema = ast.Schema()
|
|
|
|
|
|
class TestTypeChecker(base.TestCase):
|
|
|
|
def setUp(self):
|
|
self.world = {}
|
|
t1 = MinTheory('t1', self.world)
|
|
t2 = MinTheory('t2', self.world)
|
|
self.world['t1'] = t1
|
|
self.world['t2'] = t2
|
|
self.rules = ast.parse(
|
|
'l(2). l(3). p(x) :- l(x). q(x,x) :- m(x). '
|
|
'm("a"). k(x) :- t2:f(x). r(y) :- q(x,y).')
|
|
for rule in self.rules:
|
|
t1.insert(rule)
|
|
for rule in ast.parse("f(3)."):
|
|
t2.insert(rule)
|
|
self.t1 = t1
|
|
self.t2 = t2
|
|
super(TestTypeChecker, self).setUp()
|
|
|
|
def test_reset(self):
|
|
tc = typechecker.Typechecker([self.t1], self.world)
|
|
tc.reset_types()
|
|
sch1 = self.t1.schema
|
|
for (_, cols) in six.iteritems(sch1.map):
|
|
for col in cols:
|
|
self.assertIs(False, col['nullable'])
|
|
self.assertIsNone(col['type'])
|
|
|
|
def test_reset_variables(self):
|
|
tc = typechecker.Typechecker([self.t1], self.world)
|
|
tc.reset_type_environment()
|
|
env = tc.type_env
|
|
self.assertEqual(4, len(env.keys()))
|
|
for variables in six.itervalues(env):
|
|
for (v, cell) in six.iteritems(variables):
|
|
self.assertIn(v, [u'x', u'y'])
|
|
self.assertEqual(mkc(None, False), cell)
|
|
|
|
def test_type_facts(self):
|
|
tc = typechecker.Typechecker([self.t1], self.world)
|
|
tc.reset_types()
|
|
tc.reset_type_environment()
|
|
tc.type_facts(self.t1)
|
|
cols1 = self.t1.schema.map['l']
|
|
self.assertEqual(1, len(cols1))
|
|
self.assertEqual('Int', cols1[0]['type'])
|
|
self.assertIs(False, cols1[0]['nullable'])
|
|
cols2 = self.t1.schema.map['m']
|
|
self.assertEqual(1, len(cols2))
|
|
self.assertEqual('Str', cols2[0]['type'])
|
|
self.assertIs(False, cols2[0]['nullable'])
|
|
|
|
def test_type_rule(self):
|
|
rule = self.rules[2]
|
|
tc = typechecker.Typechecker([self.t1], self.world)
|
|
tc.reset_types()
|
|
tc.reset_type_environment()
|
|
tc.type_facts(self.t1)
|
|
tc.type_rule(self.t1, rule)
|
|
self.assertEqual(mkc('Int', False), tc.type_env[rule.id]['x'])
|
|
self.assertEqual('Int', self.t1.schema.map['p'][0]['type'])
|
|
self.assertIs(True, tc.work)
|
|
|
|
def test_type_all(self):
|
|
tc = typechecker.Typechecker([self.t1], self.world)
|
|
tc.type_all()
|
|
smap = self.t1.schema.map
|
|
self.assertEqual('Int', smap['p'][0]['type']) # propagation of l
|
|
self.assertEqual('Str', smap['q'][0]['type']) # propagation of m
|
|
self.assertEqual('Str', smap['q'][1]['type'])
|
|
self.assertEqual('Str', smap['r'][0]['type'])
|
|
self.assertEqual('Scalar', smap['k'][0]['type']) # prop of ext table
|