def test__parse_num(self):
        """
        Tests that the _parse_num handles integers correctly.
        """
        data = b"e"
        decoder = bencode.Decoder(data)
        with self.subTest(msg="Emtpy integer and only delim."):
            with self.assertRaises(bencode.DecodeError):
                decoder._parse_num(bencode.BencodeDelimiters.END)

        data = b"1^"
        with self.subTest(msg="Non-traditional delimiters."):
            decoder._set_data(data)
            self.assertEqual(decoder._parse_num(b"^"), 1)

        data = b"n12e"
        with self.subTest(msg="Not a digit or '-'."):
            with self.assertRaises(bencode.DecodeError):
                decoder._set_data(data)
                decoder._parse_num(bencode.BencodeDelimiters.END)

        data = b"01e"
        with self.subTest(msg="Leading zero."):
            with self.assertRaises(bencode.DecodeError):
                decoder._set_data(data)
                decoder._parse_num(bencode.BencodeDelimiters.END)

        data = b"-0e"
        with self.subTest(msg="Negative zero."):
            with self.assertRaises(bencode.DecodeError):
                decoder._set_data(data)
                decoder._parse_num(bencode.BencodeDelimiters.END)
    def test__decode_dict(self):
        """
        Tests that Decoder._decode_dict functions properly

        we leave the dictionary start delimiter b'd' off as we call into _decode_dict directly.
        """
        data = b"e"
        decoder = bencode.Decoder(data)
        with self.subTest(msg="Decode with no key in the Decoder."):
            self.assertEqual(decoder._decode_dict(), OrderedDict())

        data = b"i14e4:datae"
        with self.subTest(msg="Invalid dictionary key type."):
            with self.assertRaises(bencode.DecodeError):
                decoder._set_data(data)
                decoder._decode_dict()

        data = b"5:b key3:val5:a key3:vale"
        with self.subTest(msg="Unordered keys."):
            with self.assertRaises(bencode.DecodeError):
                decoder._set_data(data)
                decoder._decode_dict()

        data = b"3:key3:vale"
        with self.subTest(msg="Valid dictionary."):
            decoder._set_data(data)
            self.assertEqual(decoder._decode_dict(),
                             OrderedDict({"key": b"val"}))

        data = b"3:key3:valeee"
        with self.subTest(msg="Extra end delimiters."):
            decoder._set_data(data)
            self.assertEqual(decoder._decode_dict(),
                             OrderedDict({"key": b"val"}))
 def test_valid_creation(self):
     """
     Ensure __init__ works properly.
     """
     data = b"4:data"
     decoder = bencode.Decoder(data)
     self.assertIsInstance(decoder, bencode.Decoder)
     self.assertEqual(decoder._recursion_limit, 99999)
     self.assertEqual(decoder._current_iter, 0)
     self.assertEqual(decoder._data.read(), data)
    def test_invalid_creation(self):
        """
        Ensure __init__ rejects invalid arguments.
        """
        with self.subTest(msg="Invalid recursion limit."):
            with self.assertRaises(bencode.DecodeError):
                for invalid_limit in [0, -1]:
                    bencode.Decoder(bytes(), recursion_limit=invalid_limit)

        with self.subTest(msg="No data."):
            with self.assertRaises(bencode.DecodeError):
                bencode.Decoder(bytes())

        with self.subTest(msg="Invalid data types passed to Decoder."):
            with self.assertRaises(bencode.DecodeError):
                for invalid_type in [[1, 2], "string", {
                        "1": "a"
                }, (1, 2), {1, 2, 3}]:
                    # noinspection PyTypeChecker
                    bencode.Decoder(invalid_type)
    def test__decode(self):
        """
        Ensure bencode.Decoder._decode works properly.
        """
        eof = b"!"  # eof marker used in the Decoder

        with self.subTest(msg="Testing _recursion_limit is reached."):
            with self.assertRaises(bencode.BencodeRecursionError):
                decoder = bencode.Decoder(b"l")
                decoder._current_iter = 5
                decoder._recursion_limit = 4
                decoder._decode()

        decoder = bencode.Decoder(b"l")

        with self.subTest(msg="Testing _decode with nothing in the buffer."):
            decoder._data.read(1)  # exhaust the buffer
            self.assertEqual(decoder._decode(), eof)

        with self.subTest(
                msg=
                "Testing _decode with only the end of a dictionary (or list, or int)."
        ):
            decoder._set_data(bencode.BencodeDelimiters.END
                              )  # empty dictionary also ends recursion
            self.assertEqual(decoder._decode(), eof)

        with self.subTest(msg="Testing _decode with an empty dictionary."):
            decoder._set_data(b"de")
            self.assertEqual(decoder._decode(), OrderedDict())

        with self.subTest(msg="Testing _decode with an empty list."):
            decoder._set_data(b"le")
            self.assertEqual(decoder._decode(), [])

        with self.subTest(msg="Testing with an invalid bencoding key."):
            decoder._set_data(b"?")
            with self.assertRaises(bencode.DecodeError):
                decoder._decode()
    def test__decode_list(self):
        """
        Tests that Decoder._decode_list functions properly

        we leave the list start delimiter b'l' off as we call into _decode_list directly.
        """
        data = b"e"  # le
        decoder = bencode.Decoder(data)
        with self.subTest(msg="Empty list."):
            self.assertEqual(decoder._decode_list(), [])

        data = b"lee"  # llee
        with self.subTest(msg="Nested empty lists."):
            decoder._set_data(data)
            self.assertEqual(decoder._decode_list(), [[]])

        data = b"l3:valee"  # ll3:valee
        with self.subTest(msg="Populated inner list."):
            decoder._set_data(data)
            self.assertEqual(decoder._decode_list(), [[b"val"]])

        data = b"3:vall3:val3:val3:valel3:valedee"  # 3:vall3:val3:val3:valel3:valedee
        with self.subTest(msg="Populated with many types."):
            decoder._set_data(data)
            self.assertEqual(
                decoder._decode_list(),
                [b"val", [b"val", b"val", b"val"], [b"val"],
                 OrderedDict()])

        data = b"3:valeee"
        with self.subTest(msg="Extra end delimiters."):
            decoder._set_data(data)
            self.assertEqual(bencode.Decoder(data)._decode_list(), [b"val"])

        data = b"?e"
        with self.subTest(msg="Invalid list item."):
            decoder._set_data(data)
            with self.assertRaises(bencode.DecodeError):
                bencode.Decoder(data)._decode_list()
 def test__set_data(self):
     """
     Ensure _set_data works properly.
     _set_data with invalid types is already tested in test_invalid_creation
     """
     old_data = b"8:old data"
     new_data = b"8:new data"
     empty_data = b""
     decoder = bencode.Decoder(old_data)
     self.assertEqual(decoder._data.read(), old_data)
     decoder._set_data(new_data)
     self.assertEqual(decoder._data.read(), new_data)
     decoder._set_data(empty_data)
     self.assertEqual(decoder._data.read(), empty_data)
     decoder._set_data(empty_data)
     self.assertIsNone(decoder.decode())
    def test_decode_recode_compare(self):
        """
        This should probably live in test_bencode.py, but resides here now since this class creates a .torrent
        metainfo file with an external program.

        TODO: move this test to a more proper location
        """
        file_copy = os.path.abspath(
            os.path.join(os.path.dirname(__file__), "copy.torrent"))

        with open(self.external_torrent_path, 'rb') as f:
            data = f.read()
            unencoded_data = bencode.Decoder(data).decode()

            with open(file_copy, 'wb+') as ff:
                encoded_data = bencode.Encoder(unencoded_data).encode()
                ff.write(encoded_data)

        self.assertTrue(cmp(self.external_torrent_path, file_copy))
        os.remove(file_copy)
    def test__decode_bytestr(self):
        """
        Ensures that Decoder._decode_bytestr handles properly and improperly formatted data
        """
        data = b"13:nope"
        decoder = bencode.Decoder(data)
        with self.subTest(msg="Invalid string length."):
            with self.assertRaises(bencode.DecodeError):
                decoder._data.read(1)
                decoder._decode_bytestr()

        data = b"3-val"
        with self.subTest(msg="Invalid delimiter."):
            with self.assertRaises(bencode.DecodeError):
                decoder._set_data(data)
                decoder._data.read(1)
                decoder._decode_bytestr()

        data = b"34:string with spaces and bytes \x00 \x12 \x24"
        with self.subTest(msg="Valid string."):
            decoder._set_data(data)
            decoder._data.read(1)
            self.assertEqual(decoder._decode_bytestr(), data[3:])