#!/usr/bin/env python3 # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. # pyre-strict import pickle import unittest from typing import cast, Type, TypeVar from testing.types import BadMembers, Color, ColorGroups, File, Kind, Perm from thrift.lib.py3.test.auto_migrate_util import brokenInAutoMigrate from thrift.py3.common import Protocol from thrift.py3.serializer import deserialize, serialize from thrift.py3.types import BadEnum, Enum, Flag _E = TypeVar("_E", bound=Enum) class EnumTests(unittest.TestCase): def test_bad_member_names(self) -> None: self.assertIsInstance(BadMembers.name_, BadMembers) self.assertIsInstance(BadMembers.value_, BadMembers) self.assertIn("name_", BadMembers.__members__) self.assertIn("value_", BadMembers.__members__) @brokenInAutoMigrate() def test_normal_enum(self) -> None: with self.assertRaises(TypeError): # Enums are not ints # pyre-fixme[6]: Expected `Optional[Kind]` for 2nd param but got `int`. File(name="/etc/motd", type=8) x = File(name="/etc", type=Kind.DIR) self.assertIsInstance(x.type, Kind) self.assertEqual(x.type, Kind.DIR) self.assertNotEqual(x.type, Kind.SOCK) self.assertNotEqual(x.type, 4, "Enums are not Ints") self.assertNotIsInstance(4, Kind, "Enums are not Ints") self.assertIn(x.type, Kind) self.assertEqual(int(x.type), 4) def test_enum_value_rename(self) -> None: """The value name is None but we auto rename it to None_""" x = deserialize(File, b'{"name":"blah", "type":0}', Protocol.JSON) self.assertEqual(x.type, Kind.None_) def test_protocol_int_conversion(self) -> None: self.assertEqual(Protocol.BINARY.value, 0) self.assertEqual(Protocol.DEPRECATED_VERBOSE_JSON.value, 1) self.assertEqual(Protocol.COMPACT.value, 2) self.assertEqual(Protocol.JSON.value, 5) def test_bad_enum_hash_same(self) -> None: x = deserialize(File, b'{"name": "something", "type": 64}', Protocol.JSON) y = deserialize(File, b'{"name": "something", "type": 64}', Protocol.JSON) self.assertEqual(hash(x), hash(y)) self.assertEqual(hash(x.type), hash(y.type)) self.assertFalse(x.type is y.type) self.assertEqual(x.type, y.type) self.assertFalse(x.type != y.type) @brokenInAutoMigrate() def test_bad_enum_in_struct(self) -> None: x = deserialize(File, b'{"name": "something", "type": 64}', Protocol.JSON) self.assertBadEnum(cast(BadEnum, x.type), Kind, 64) @brokenInAutoMigrate() def test_bad_enum_in_list_index(self) -> None: x = deserialize(ColorGroups, b'{"color_list": [1, 5, 0]}', Protocol.JSON) self.assertEqual(len(x.color_list), 3) self.assertEqual(x.color_list[0], Color.blue) self.assertBadEnum(cast(BadEnum, x.color_list[1]), Color, 5) self.assertEqual(x.color_list[2], Color.red) @brokenInAutoMigrate() def test_bad_enum_in_list_iter(self) -> None: x = deserialize(ColorGroups, b'{"color_list": [1, 5, 0]}', Protocol.JSON) for idx, v in enumerate(x.color_list): if idx == 0: self.assertEqual(v, Color.blue) elif idx == 1: self.assertBadEnum(cast(BadEnum, v), Color, 5) else: self.assertEqual(v, Color.red) @brokenInAutoMigrate() def test_bad_enum_in_list_reverse(self) -> None: x = deserialize(ColorGroups, b'{"color_list": [1, 5, 0]}', Protocol.JSON) for idx, v in enumerate(reversed(x.color_list)): if idx == 0: self.assertEqual(v, Color.red) elif idx == 1: self.assertBadEnum(cast(BadEnum, v), Color, 5) else: self.assertEqual(v, Color.blue) @brokenInAutoMigrate() def test_bad_enum_in_set_iter(self) -> None: x = deserialize(ColorGroups, b'{"color_set": [1, 5, 0]}', Protocol.JSON) for v in x.color_set: if v not in (Color.blue, Color.red): self.assertBadEnum(cast(BadEnum, v), Color, 5) @brokenInAutoMigrate() def test_bad_enum_in_map_lookup(self) -> None: json = b'{"color_map": {"1": 2, "0": 5, "6": 1, "7": 8}}' x = deserialize(ColorGroups, json, Protocol.JSON) val = x.color_map[Color.red] self.assertBadEnum(cast(BadEnum, val), Color, 5) @brokenInAutoMigrate() def test_bad_enum_in_map_iter(self) -> None: json = b'{"color_map": {"1": 2, "0": 5, "6": 1, "7": 8}}' x = deserialize(ColorGroups, json, Protocol.JSON) s = set() for k in x.color_map: s.add(k) self.assertEqual(len(s), 4) s.discard(Color.red) s.discard(Color.blue) lst = sorted(s, key=lambda e: cast(BadEnum, e).value) self.assertBadEnum(cast(BadEnum, lst[0]), Color, 6) self.assertBadEnum(cast(BadEnum, lst[1]), Color, 7) @brokenInAutoMigrate() def test_bad_enum_in_map_values(self) -> None: json = b'{"color_map": {"1": 2, "0": 5, "6": 1, "7": 8}}' x = deserialize(ColorGroups, json, Protocol.JSON) s = set() for k in x.color_map.values(): s.add(k) self.assertEqual(len(s), 4) s.discard(Color.green) s.discard(Color.blue) lst = sorted(s, key=lambda e: cast(BadEnum, e).value) self.assertBadEnum(cast(BadEnum, lst[0]), Color, 5) self.assertBadEnum(cast(BadEnum, lst[1]), Color, 8) @brokenInAutoMigrate() def test_bad_enum_in_map_items(self) -> None: json = b'{"color_map": {"1": 2, "0": 5, "6": 1, "7": 8}}' x = deserialize(ColorGroups, json, Protocol.JSON) for k, v in x.color_map.items(): if k == Color.blue: self.assertEqual(v, Color.green) elif k == Color.red: self.assertBadEnum(cast(BadEnum, v), Color, 5) else: ck = cast(BadEnum, k) if ck.value == 6: self.assertEqual(v, Color.blue) else: self.assertBadEnum(cast(BadEnum, v), Color, 8) def assertBadEnum(self, e: BadEnum, cls: Type[_E], val: int) -> None: self.assertIsInstance(e, BadEnum) self.assertEqual(e.value, val) self.assertEqual(e.enum, cls) self.assertEqual(int(e), val) def test_pickle(self) -> None: serialized = pickle.dumps(Color.green) green = pickle.loads(serialized) self.assertIs(green, Color.green) @brokenInAutoMigrate() def test_adding_member(self) -> None: with self.assertRaises(TypeError): # pyre-fixme[16]: `Type` has no attribute `black`. Color.black = 3 @brokenInAutoMigrate() def etst_delete(self) -> None: with self.assertRaises(TypeError): del Color.red def test_bool_of_class(self) -> None: self.assertTrue(bool(Color)) def test_bool_of_members(self) -> None: self.assertTrue(bool(Color.blue)) @brokenInAutoMigrate() def test_changing_member(self) -> None: with self.assertRaises(TypeError): # pyre-fixme[8]: Attribute has type `Color`; used as `str`. Color.red = "lol" @brokenInAutoMigrate() def test_contains(self) -> None: self.assertIn(Color.blue, Color) self.assertNotIn(1, Color) def test_hash(self) -> None: colors = {} colors[Color.red] = 0xFF0000 colors[Color.blue] = 0x0000FF colors[Color.green] = 0x00FF00 self.assertEqual(colors[Color.green], 0x00FF00) def test_enum_in_enum_out(self) -> None: self.assertIs(Color(Color.blue), Color.blue) def test_enum_value(self) -> None: self.assertEqual(Color.red.value, 0) @brokenInAutoMigrate() def test_enum(self) -> None: lst = list(Color) self.assertEqual(len(lst), len(Color)) self.assertEqual(len(Color), 3) self.assertEqual([Color.red, Color.blue, Color.green], lst) for i, color in enumerate("red blue green".split(), 0): e = Color(i) self.assertEqual(e, getattr(Color, color)) self.assertEqual(e.value, i) self.assertNotEqual(e, i) self.assertEqual(e.name, color) self.assertIn(e, Color) self.assertIs(type(e), Color) self.assertIsInstance(e, Color) self.assertEqual(str(e), "Color." + color) self.assertEqual(int(e), i) self.assertEqual(repr(e), f"") @brokenInAutoMigrate() def test_insinstance_Enum(self) -> None: self.assertIsInstance(Color.red, Enum) self.assertTrue(issubclass(Color, Enum)) class FlagTests(unittest.TestCase): @brokenInAutoMigrate() def test_flag_enum(self) -> None: with self.assertRaises(TypeError): # flags are not ints # pyre-fixme[6]: Expected `Optional[Perm]` for 2nd param but got `int`. File(name="/etc/motd", permissions=4) x = File(name="/bin/sh", permissions=Perm.read | Perm.execute) self.assertIsInstance(x.permissions, Perm) self.assertEqual(x.permissions, Perm.read | Perm.execute) self.assertNotIsInstance(2, Perm, "Flags are not ints") self.assertEqual(int(x.permissions), 5) x = File(name="") self.assertFalse(x.permissions) self.assertIsInstance(x.permissions, Perm) def test_flag_enum_serialization_roundtrip(self) -> None: x = File(name="/dev/null", type=Kind.CHAR, permissions=Perm.read | Perm.write) y = deserialize(File, serialize(x)) self.assertEqual(x, y) self.assertEqual(x.permissions, Perm.read | Perm.write) self.assertIsInstance(x.permissions, Perm) def test_zero(self) -> None: zero = Perm(0) self.assertNotIn(zero, Perm) self.assertIsInstance(zero, Perm) def test_combination(self) -> None: combo = Perm(Perm.read.value | Perm.execute.value) self.assertNotIn(combo, Perm) self.assertIsInstance(combo, Perm) self.assertIs(combo, Perm.read | Perm.execute) def test_is(self) -> None: allp = Perm(7) self.assertIs(allp, Perm(7)) def test_invert(self) -> None: x = Perm(-2) self.assertIs(x, Perm.read | Perm.write) @brokenInAutoMigrate() def test_insinstance_Flag(self) -> None: self.assertIsInstance(Perm.read, Flag) self.assertTrue(issubclass(Perm, Flag)) self.assertIsInstance(Perm.read, Enum) self.assertTrue(issubclass(Perm, Enum)) def test_combo_in_call(self) -> None: x = Perm(7) self.assertIs(x, Perm.read | Perm.write | Perm.execute) def test_combo_repr(self) -> None: x = Perm(7) self.assertEqual("", repr(x))