Matteo Scandolo | d2044a4 | 2017-08-07 16:08:28 -0700 | [diff] [blame] | 1 | # Copyright 2017-present Open Networking Foundation |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | |
Zack Williams | 9a42f87 | 2019-02-15 17:56:04 -0700 | [diff] [blame] | 16 | from __future__ import absolute_import |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 17 | import unittest |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 18 | from xosgenx.generator import XOSProcessor, XOSProcessorArgs |
| 19 | from helpers import FakeObject, XProtoTestHelpers |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 20 | |
| 21 | """ |
| 22 | The tests below convert the policy logic expression |
| 23 | into Python, set up an appropriate environment and execute the Python. |
| 24 | """ |
| 25 | |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 26 | |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 27 | class XProtoPolicyTest(unittest.TestCase): |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 28 | def test_annotation(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 29 | xproto = """ |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 30 | policy true_policy < True > |
| 31 | |
| 32 | message always::true_policy { |
| 33 | required int still = 9; |
| 34 | } |
| 35 | """ |
| 36 | |
| 37 | target = XProtoTestHelpers.write_tmp_target("{{ proto.messages.0 }}") |
| 38 | |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 39 | args = XOSProcessorArgs() |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 40 | args.inputs = xproto |
| 41 | args.target = target |
| 42 | |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 43 | output = XOSProcessor.process(args) |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 44 | self.assertIn("true_policy", output) |
| 45 | |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 46 | def test_constant(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 47 | xproto = """ |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 48 | policy true_policy < True > |
| 49 | """ |
| 50 | |
| 51 | target = XProtoTestHelpers.write_tmp_target("{{ proto.policies.true_policy }}") |
| 52 | |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 53 | args = XOSProcessorArgs() |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 54 | args.inputs = xproto |
| 55 | args.target = target |
| 56 | |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 57 | output = XOSProcessor.process(args).replace("t", "T") |
| 58 | self.assertTrue(eval(output)) |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 59 | |
Sapan Bhatia | 5ea307d | 2017-07-19 00:13:21 -0400 | [diff] [blame] | 60 | def test_function_term(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 61 | xproto = """ |
Sapan Bhatia | 5ea307d | 2017-07-19 00:13:21 -0400 | [diff] [blame] | 62 | policy slice_user < slice.user.compute_is_admin() > |
| 63 | """ |
| 64 | |
| 65 | target = XProtoTestHelpers.write_tmp_target("{{ proto.policies.slice_user }}") |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 66 | args = XOSProcessorArgs() |
Sapan Bhatia | 5ea307d | 2017-07-19 00:13:21 -0400 | [diff] [blame] | 67 | args.inputs = xproto |
| 68 | args.target = target |
| 69 | |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 70 | output = XOSProcessor.process(args) |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 71 | |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 72 | slice = FakeObject() |
| 73 | slice.user = FakeObject() |
Sapan Bhatia | 5ea307d | 2017-07-19 00:13:21 -0400 | [diff] [blame] | 74 | slice.user.compute_is_admin = lambda: True |
| 75 | |
| 76 | expr = eval(output) |
| 77 | self.assertTrue(expr) |
| 78 | |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 79 | def test_term(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 80 | xproto = """ |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 81 | policy slice_user < slice.user.is_admin > |
| 82 | """ |
| 83 | |
| 84 | target = XProtoTestHelpers.write_tmp_target("{{ proto.policies.slice_user }}") |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 85 | args = XOSProcessorArgs() |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 86 | args.inputs = xproto |
| 87 | args.target = target |
| 88 | |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 89 | output = XOSProcessor.process(args) |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 90 | |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 91 | slice = FakeObject() |
| 92 | slice.user = FakeObject() |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 93 | slice.user.is_admin = True |
| 94 | |
| 95 | expr = eval(output) |
| 96 | self.assertTrue(expr) |
| 97 | |
| 98 | def test_num_constant(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 99 | xproto = """ |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 100 | policy slice_user < slice.user.age = 57 > |
| 101 | """ |
| 102 | |
| 103 | target = XProtoTestHelpers.write_tmp_target("{{ proto.policies.slice_user }}") |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 104 | args = XOSProcessorArgs() |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 105 | args.inputs = xproto |
| 106 | args.target = target |
| 107 | |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 108 | output = XOSProcessor.process(args) |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 109 | |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 110 | slice = FakeObject() |
| 111 | slice.user = FakeObject() |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 112 | slice.user.is_admin = True |
| 113 | |
| 114 | expr = eval(output) |
| 115 | self.assertTrue(expr) |
| 116 | |
| 117 | def test_string_constant(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 118 | xproto = """ |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 119 | policy slice_user < slice.user.email = "admin@opencord.org" > |
| 120 | """ |
| 121 | |
| 122 | target = XProtoTestHelpers.write_tmp_target("{{ proto.policies.slice_user }}") |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 123 | args = XOSProcessorArgs() |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 124 | args.inputs = xproto |
| 125 | args.target = target |
| 126 | |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 127 | output = XOSProcessor.process(args) |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 128 | |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 129 | slice = FakeObject() |
| 130 | slice.user = FakeObject() |
Sapan Bhatia | 3e3c1cd | 2017-07-15 01:35:44 -0400 | [diff] [blame] | 131 | slice.user.is_admin = True |
| 132 | |
| 133 | expr = eval(output) |
| 134 | self.assertTrue(expr) |
| 135 | |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 136 | def test_equal(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 137 | xproto = """ |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 138 | policy slice_user < slice.user = obj.user > |
| 139 | """ |
| 140 | |
| 141 | target = XProtoTestHelpers.write_tmp_target("{{ proto.policies.slice_user }}") |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 142 | args = XOSProcessorArgs() |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 143 | args.inputs = xproto |
| 144 | args.target = target |
| 145 | |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 146 | output = XOSProcessor.process(args) |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 147 | |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 148 | slice = FakeObject() |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 149 | slice.user = "twin" |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 150 | obj = FakeObject() |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 151 | obj.user = "twin" |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 152 | |
| 153 | (op, operands), = eval(output).items() |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 154 | expr = op.join(operands).replace("=", "==") |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 155 | |
| 156 | self.assertTrue(eval(expr)) |
| 157 | |
| 158 | def test_bin(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 159 | xproto = """ |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 160 | policy slice_admin < slice.is_admin | obj.empty > |
| 161 | """ |
| 162 | target = XProtoTestHelpers.write_tmp_target("{{ proto.policies.slice_admin }}") |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 163 | args = XOSProcessorArgs() |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 164 | args.inputs = xproto |
| 165 | args.target = target |
| 166 | |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 167 | output = XOSProcessor.process(args) |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 168 | |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 169 | slice = FakeObject() |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 170 | slice.is_admin = False |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 171 | obj = FakeObject() |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 172 | obj.empty = [] |
| 173 | |
| 174 | (op, operands), = eval(output).items() |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 175 | expr = op.join(operands).replace("|", " or ") |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 176 | |
| 177 | self.assertFalse(eval(expr)) |
| 178 | |
Sapan Bhatia | 5ea307d | 2017-07-19 00:13:21 -0400 | [diff] [blame] | 179 | def test_implies(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 180 | xproto = """ |
Sapan Bhatia | 5ea307d | 2017-07-19 00:13:21 -0400 | [diff] [blame] | 181 | policy implies < obj.name -> obj.creator > |
| 182 | """ |
| 183 | target = XProtoTestHelpers.write_tmp_target("{{ proto.policies.implies }}") |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 184 | args = XOSProcessorArgs() |
Sapan Bhatia | 5ea307d | 2017-07-19 00:13:21 -0400 | [diff] [blame] | 185 | args.inputs = xproto |
| 186 | args.target = target |
| 187 | |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 188 | output = XOSProcessor.process(args) |
Sapan Bhatia | 5ea307d | 2017-07-19 00:13:21 -0400 | [diff] [blame] | 189 | |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 190 | slice = FakeObject() |
Sapan Bhatia | 5ea307d | 2017-07-19 00:13:21 -0400 | [diff] [blame] | 191 | slice.is_admin = False |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 192 | obj = FakeObject() |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 193 | obj.name = "Thing 1" |
Sapan Bhatia | 5ea307d | 2017-07-19 00:13:21 -0400 | [diff] [blame] | 194 | obj.creator = None |
| 195 | |
| 196 | (op, operands), = eval(output).items() |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 197 | expr = "not " + op.join(operands).replace("->", " or ") |
Sapan Bhatia | 5ea307d | 2017-07-19 00:13:21 -0400 | [diff] [blame] | 198 | |
| 199 | self.assertFalse(eval(expr)) |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 200 | |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 201 | def test_exists(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 202 | xproto = """ |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 203 | policy privilege < exists Privilege: Privilege.object_id = obj.id > |
| 204 | """ |
| 205 | |
| 206 | target = XProtoTestHelpers.write_tmp_target("{{ proto.policies.privilege }} ") |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 207 | args = XOSProcessorArgs() |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 208 | args.inputs = xproto |
| 209 | args.target = target |
| 210 | |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 211 | output = XOSProcessor.process(args) |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 212 | |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 213 | Privilege = FakeObject() |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 214 | Privilege.object_id = 1 |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 215 | obj = FakeObject() |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 216 | obj.id = 1 |
| 217 | |
| 218 | (op, operands), = eval(output).items() |
| 219 | (op2, operands2), = operands[1].items() |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 220 | expr = op2.join(operands2).replace("=", "==") |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 221 | |
| 222 | self.assertTrue(eval(expr)) |
| 223 | |
Sapan Bhatia | d3fcb66 | 2017-07-25 21:13:48 -0400 | [diff] [blame] | 224 | def test_policy_function(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 225 | xproto = """ |
Sapan Bhatia | d3fcb66 | 2017-07-25 21:13:48 -0400 | [diff] [blame] | 226 | policy slice_policy < exists Privilege: Privilege.object_id = obj.id > |
| 227 | policy network_slice_policy < *slice_policy(slice) > |
| 228 | """ |
| 229 | |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 230 | target = XProtoTestHelpers.write_tmp_target( |
| 231 | "{{ proto.policies.network_slice_policy }} " |
| 232 | ) |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 233 | args = XOSProcessorArgs() |
Sapan Bhatia | d3fcb66 | 2017-07-25 21:13:48 -0400 | [diff] [blame] | 234 | args.inputs = xproto |
| 235 | args.target = target |
| 236 | |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 237 | output = XOSProcessor.process(args) |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 238 | |
Sapan Bhatia | d3fcb66 | 2017-07-25 21:13:48 -0400 | [diff] [blame] | 239 | (op, operands), = eval(output).items() |
| 240 | |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 241 | self.assertIn("slice_policy", operands) |
| 242 | self.assertIn("slice", operands) |
Sapan Bhatia | d3fcb66 | 2017-07-25 21:13:48 -0400 | [diff] [blame] | 243 | |
| 244 | def test_policy_missing_function(self): |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 245 | xproto = """ |
Sapan Bhatia | d3fcb66 | 2017-07-25 21:13:48 -0400 | [diff] [blame] | 246 | policy slice_policy < exists Privilege: Privilege.object_id = obj.id > |
| 247 | policy network_slice_policy < *slice_policyX(slice) > |
| 248 | """ |
| 249 | |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 250 | target = XProtoTestHelpers.write_tmp_target( |
| 251 | "{{ proto.policies.network_slice_policy }} " |
| 252 | ) |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 253 | args = XOSProcessorArgs() |
Sapan Bhatia | d3fcb66 | 2017-07-25 21:13:48 -0400 | [diff] [blame] | 254 | args.inputs = xproto |
| 255 | args.target = target |
| 256 | |
| 257 | with self.assertRaises(Exception): |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 258 | output = XOSProcessor.process(args) |
Sapan Bhatia | d3fcb66 | 2017-07-25 21:13:48 -0400 | [diff] [blame] | 259 | |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 260 | def test_forall(self): |
| 261 | # This one we only parse |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 262 | xproto = """ |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 263 | policy instance < forall Instance: exists Credential: Credential.obj_id = Instance.obj_id > |
| 264 | """ |
| 265 | |
| 266 | target = XProtoTestHelpers.write_tmp_target("{{ proto.policies.instance }}") |
| 267 | |
Scott Baker | 1f7791d | 2018-10-04 13:21:20 -0700 | [diff] [blame] | 268 | args = XOSProcessorArgs() |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 269 | args.inputs = xproto |
| 270 | args.target = target |
| 271 | |
Sapan Bhatia | bfb233a | 2018-02-09 14:53:09 -0800 | [diff] [blame] | 272 | output = XOSProcessor.process(args) |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 273 | (op, operands), = eval(output).items() |
| 274 | |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 275 | self.assertEqual(op, "forall") |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 276 | |
| 277 | |
Zack Williams | 045b63d | 2019-01-22 16:30:57 -0700 | [diff] [blame] | 278 | if __name__ == "__main__": |
Sapan Bhatia | db183c2 | 2017-06-23 02:47:42 -0700 | [diff] [blame] | 279 | unittest.main() |