169 lines
5.6 KiB
Python
169 lines
5.6 KiB
Python
# Copyright 2023 The TensorFlow Authors. All Rights Reserved.
|
|
#
|
|
# 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.
|
|
#
|
|
# ==============================================================================
|
|
"""Tests for ProtoSplitter."""
|
|
|
|
import os
|
|
import random
|
|
import string
|
|
from typing import Optional
|
|
|
|
from absl.testing import parameterized
|
|
import riegeli
|
|
|
|
from tensorflow.python.platform import test
|
|
from tensorflow.tools.proto_splitter import chunk_pb2
|
|
from tensorflow.tools.proto_splitter import split
|
|
from tensorflow.tools.proto_splitter.testdata import test_message_pb2
|
|
|
|
|
|
class RepeatedStringSplitter(split.ComposableSplitter):
|
|
"""Splits a RepeatedString proto with N repeated strings into N chunks."""
|
|
|
|
def __init__(self, proto, **kwargs):
|
|
if not isinstance(proto, test_message_pb2.RepeatedString):
|
|
raise TypeError("Can only split RepeatedString type protos")
|
|
|
|
empty_strings = not bool(proto.strings)
|
|
super().__init__(proto, proto_as_initial_chunk=empty_strings, **kwargs)
|
|
|
|
def build_chunks(self):
|
|
for n, s in enumerate(self._proto.strings):
|
|
b = bytes(s, encoding="utf-8")
|
|
self.add_chunk(b, ["strings", n])
|
|
self._proto.ClearField("strings")
|
|
|
|
|
|
def _random_string(length):
|
|
return bytes(
|
|
"".join(random.choices(string.ascii_lowercase, k=length)),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
class SplitRepeatedStringTest(test.TestCase):
|
|
|
|
def _to_proto(self, strings):
|
|
return test_message_pb2.RepeatedString(strings=strings)
|
|
|
|
def testSplit(self):
|
|
s = RepeatedStringSplitter(test_message_pb2.RepeatedString(strings=[]))
|
|
chunks = s.split()[0]
|
|
self.assertLen(chunks, 1)
|
|
self.assertIsInstance(chunks[0], test_message_pb2.RepeatedString)
|
|
|
|
s = RepeatedStringSplitter(
|
|
test_message_pb2.RepeatedString(strings=["a", "b", "c"])
|
|
)
|
|
chunks, chunked_message = s.split()
|
|
self.assertListEqual([b"a", b"b", b"c"], chunks)
|
|
self.assertLen(chunked_message.chunked_fields, 3)
|
|
|
|
def testWrite(self):
|
|
path = os.path.join(self.create_tempdir(), "split-repeat")
|
|
data = [_random_string(5), _random_string(10), _random_string(15)]
|
|
returned_path = RepeatedStringSplitter(
|
|
test_message_pb2.RepeatedString(strings=data)
|
|
).write(path)
|
|
self.assertEqual(returned_path, f"{path}.cpb")
|
|
|
|
with riegeli.RecordReader(open(f"{path}.cpb", "rb")) as reader:
|
|
self.assertTrue(reader.check_file_format())
|
|
records = list(reader.read_records())
|
|
self.assertLen(records, 4)
|
|
|
|
proto = chunk_pb2.ChunkMetadata()
|
|
proto.ParseFromString(records[-1])
|
|
self.assertLen(proto.message.chunked_fields, 3)
|
|
self.assertFalse(proto.message.HasField("chunk_index"))
|
|
|
|
expected_indices = [0, 1, 2]
|
|
# Check that the chunk indices and info are correct.
|
|
for expected_index, expected_data, chunk in zip(
|
|
expected_indices, data, proto.message.chunked_fields
|
|
):
|
|
i = chunk.message.chunk_index
|
|
self.assertEqual(expected_index, i)
|
|
|
|
chunk_info = proto.chunks[i]
|
|
self.assertEqual(chunk_pb2.ChunkInfo.Type.BYTES, chunk_info.type)
|
|
self.assertEqual(len(expected_data), chunk_info.size)
|
|
reader.seek_numeric(chunk_info.offset)
|
|
self.assertEqual(expected_data, reader.read_record())
|
|
|
|
def test_child_splitter(self):
|
|
proto = test_message_pb2.RepeatedRepeatedString(
|
|
rs=[
|
|
test_message_pb2.RepeatedString(strings=["a", "b", "c"]),
|
|
test_message_pb2.RepeatedString(strings=["d", "e"]),
|
|
]
|
|
)
|
|
splitter = NoOpSplitter(proto)
|
|
|
|
self.assertLen(splitter.split()[0], 1)
|
|
splitter.add_chunk("", [])
|
|
self.assertLen(splitter.split()[0], 2)
|
|
|
|
child = RepeatedStringSplitter(
|
|
proto.rs[0], parent_splitter=splitter, fields_in_parent=["rs", 0]
|
|
)
|
|
child.build_chunks()
|
|
|
|
self.assertLen(splitter.split()[0], 5) # Adds 3 chunks.
|
|
|
|
RepeatedStringSplitter(
|
|
proto.rs[1], parent_splitter=splitter, fields_in_parent=["rs", 1]
|
|
).build_chunks()
|
|
|
|
self.assertLen(splitter.split()[0], 7) # Adds 2 chunks.
|
|
|
|
with self.assertRaisesRegex(
|
|
ValueError, " `split` method should not be called directly"
|
|
):
|
|
child.split()
|
|
path = os.path.join(self.create_tempdir(), "na-split")
|
|
with self.assertRaisesRegex(
|
|
ValueError, " `write` method should not be called directly"
|
|
):
|
|
child.write(path)
|
|
|
|
|
|
class NoOpSplitter(split.ComposableSplitter):
|
|
|
|
def build_chunks(self):
|
|
pass
|
|
|
|
|
|
class NoOpSplitterTest(test.TestCase, parameterized.TestCase):
|
|
|
|
@parameterized.parameters([None, "uncompressed"])
|
|
def testWriteNoChunks(self, writer_options: Optional[str]):
|
|
path = os.path.join(self.create_tempdir(), "split-none")
|
|
proto = test_message_pb2.RepeatedString(strings=["a", "bc", "de"])
|
|
returned_path = NoOpSplitter(proto).write(path, writer_options)
|
|
|
|
expected_file_path = path + ".pb"
|
|
self.assertTrue(os.path.isfile(expected_file_path))
|
|
self.assertEqual(returned_path, expected_file_path)
|
|
|
|
parsed_proto = test_message_pb2.RepeatedString()
|
|
with open(expected_file_path, "rb") as f:
|
|
parsed_proto.ParseFromString(f.read())
|
|
self.assertProtoEquals(proto, parsed_proto)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test.main()
|