Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions tensorboard/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,8 @@ def _info_from_string(info_string):
A `TensorBoardInfo` value.

Raises:
ValueError: If the provided string is not valid JSON, or if it does
not represent a JSON object with a "version" field whose value is
`tensorboard.version.VERSION`, or if it has the wrong set of
fields, or if at least one field is of invalid type.
ValueError: If the provided string is not valid JSON, or if it is
missing any required fields, or if any field is of incorrect type.
"""

try:
Expand All @@ -151,27 +149,28 @@ def _info_from_string(info_string):
raise ValueError("invalid JSON: %r" % (info_string,))
if not isinstance(json_value, dict):
raise ValueError("not a JSON object: %r" % (json_value,))
if json_value.get("version") != version.VERSION:
raise ValueError("incompatible version: %r" % (json_value,))
expected_keys = frozenset(_TENSORBOARD_INFO_FIELDS)
actual_keys = frozenset(json_value)
if expected_keys != actual_keys:
missing_keys = expected_keys - actual_keys
if missing_keys:
raise ValueError(
"bad keys on TensorBoardInfo (missing: %s; extraneous: %s)"
% (expected_keys - actual_keys, actual_keys - expected_keys)
"TensorBoardInfo missing keys: %r"
% (sorted(missing_keys),)
)
# For forward compatibility, silently ignore unknown keys.

# Validate and deserialize fields.
fields = {}
for key in _TENSORBOARD_INFO_FIELDS:
field_type = _TENSORBOARD_INFO_FIELDS[key]
if not isinstance(json_value[key], field_type.serialized_type):
raise ValueError(
"expected %r of type %s, but found: %r" %
(key, field_type.serialized_type, json_value[key])
)
json_value[key] = field_type.deserialize(json_value[key])
fields[key] = field_type.deserialize(json_value[key])

return TensorBoardInfo(**json_value)
return TensorBoardInfo(**fields)


def cache_key(working_directory, arguments, configure_kwargs):
Expand Down Expand Up @@ -295,6 +294,9 @@ def get_all():
contain extraneous entries if TensorBoard processes exited uncleanly
(e.g., with SIGKILL or SIGQUIT).

Entries in the info directory that do not represent valid
`TensorBoardInfo` values will be silently ignored.

Returns:
A fresh list of `TensorBoardInfo` objects.
"""
Expand All @@ -315,7 +317,8 @@ def get_all():
try:
info = _info_from_string(contents)
except ValueError:
tb_logging.get_logger().warning(
# Ignore unrecognized files, logging at debug only.
tb_logging.get_logger().debug(
"invalid info file: %r",
filepath,
exc_info=True,
Expand Down
28 changes: 11 additions & 17 deletions tensorboard/manager_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,30 +111,24 @@ def test_deserialization_rejects_missing_version(self):
with six.assertRaisesRegex(
self,
ValueError,
"incompatible version:"):
re.escape("missing keys: ['version']")):
manager._info_from_string(bad_input)

def test_deserialization_rejects_bad_version(self):
def test_deserialization_accepts_future_version(self):
info = _make_info()
json_value = json.loads(manager._info_to_string(info))
json_value["version"] = "not likely"
bad_input = json.dumps(json_value)
with six.assertRaisesRegex(
self,
ValueError,
"incompatible version:.*not likely"):
manager._info_from_string(bad_input)
json_value["version"] = "99.99.99a20991232"
input_ = json.dumps(json_value)
result = manager._info_from_string(input_)
self.assertEqual(result.version, "99.99.99a20991232")

def test_deserialization_rejects_extra_keys(self):
def test_deserialization_ignores_extra_keys(self):
info = _make_info()
json_value = json.loads(manager._info_to_string(info))
json_value["unlikely"] = "story"
bad_input = json.dumps(json_value)
with six.assertRaisesRegex(
self,
ValueError,
"bad keys on TensorBoardInfo"):
manager._info_from_string(bad_input)
result = manager._info_from_string(bad_input)
self.assertIsInstance(result, manager.TensorBoardInfo)

def test_deserialization_rejects_missing_keys(self):
info = _make_info()
Expand All @@ -144,7 +138,7 @@ def test_deserialization_rejects_missing_keys(self):
with six.assertRaisesRegex(
self,
ValueError,
"bad keys on TensorBoardInfo"):
re.escape("missing keys: ['start_time']")):
manager._info_from_string(bad_input)

def test_deserialization_rejects_bad_types(self):
Expand Down Expand Up @@ -401,7 +395,7 @@ def test_get_all_ignores_bad_files(self):
with open(os.path.join(self.info_dir, "pid-9012.info"), "w") as outfile:
outfile.write('if a tbinfo has st_mode==0, does it make a sound?\n')
os.chmod(os.path.join(self.info_dir, "pid-9012.info"), 0o000)
with mock.patch.object(tb_logging.get_logger(), "warning") as fn:
with mock.patch.object(tb_logging.get_logger(), "debug") as fn:
self.assertEqual(manager.get_all(), [])
self.assertEqual(fn.call_count, 2) # 2 invalid, 1 unreadable (silent)

Expand Down