Read the local library list as JSON instead of splitting columns
get_local_libraries unpacked "line.rsplit(maxsplit=3)" into three names, but maxsplit=3 yields up to four fields. seaf-cli prints "name id path" separated by whitespace, so any library whose name contains a space produced a fourth field and raised ValueError, which nothing caught. The failure only appeared after the first run: the list is empty until a library has been synced, so a container would work when it was set up and then crash on every restart. seaf-cli list --json returns name, id and path as structured fields, which display output cannot represent unambiguously for names or paths containing whitespace. Its exit code is now checked and unreadable output is reported as a daemon error instead of a traceback. The image smoke check asserts that the installed seaf-cli offers --json and --token, so a future version bump that drops either fails the build rather than the container.
This commit is contained in:
1 parent
8043ac1409
commit
b75863ba51
4 files changed
+73
-9
No files matched your search
@@ -1,12 +1,14 @@
|
||||
"""Every subprocess must be an argument list, never a shell string."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
from dsc import client as client_module
|
||||
from dsc import const
|
||||
from dsc.errors import DaemonError
|
||||
from dsc.client import SeafileClient
|
||||
from dsc.misc import hide_secrets, user_cmd
|
||||
|
||||
@@ -157,3 +159,57 @@ def test_configure_passes_option_values_as_arguments(client, calls, monkeypatch)
|
||||
assert prefix + ["-k", "upload_limit", "-v", "100"] in argvs
|
||||
# An option that seaf-cli does not know about is never forwarded.
|
||||
assert not any("ignored" in argv for argv in argvs)
|
||||
|
||||
|
||||
def run_returning(monkeypatch, stdout=b"", returncode=0):
|
||||
"""Run seaf-cli commands that produce the given output."""
|
||||
calls = []
|
||||
|
||||
def fake_run(argv, **kwargs):
|
||||
calls.append(argv)
|
||||
return subprocess.CompletedProcess(argv, returncode, stdout=stdout)
|
||||
|
||||
monkeypatch.setattr(subprocess, "run", fake_run)
|
||||
return calls
|
||||
|
||||
|
||||
def test_local_libraries_are_read_as_json(client, monkeypatch):
|
||||
out = json.dumps([
|
||||
{"name": "Docs", "id": "id-1", "path": "/dsc/seafile/Docs"},
|
||||
]).encode()
|
||||
calls = run_returning(monkeypatch, out)
|
||||
|
||||
assert client.get_local_libraries() == {"id-1"}
|
||||
assert "--json" in calls[-1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name, path", [
|
||||
# Display output is whitespace separated, so these used to be unparseable.
|
||||
("My Docs", "/dsc/seafile/My_Docs"),
|
||||
("Docs", "/mnt/my libraries/Docs"),
|
||||
("My Docs", "/mnt/my libraries/My Docs"),
|
||||
("Ünïcödé ☂", "/dsc/seafile/Ünïcödé_☂"),
|
||||
("tab\tname", "/dsc/seafile/tab_name"),
|
||||
])
|
||||
def test_local_libraries_survive_whitespace_in_names_and_paths(client, monkeypatch, name, path):
|
||||
out = json.dumps([{"name": name, "id": "id-1", "path": path}]).encode()
|
||||
run_returning(monkeypatch, out)
|
||||
|
||||
assert client.get_local_libraries() == {"id-1"}
|
||||
|
||||
|
||||
def test_no_local_libraries_gives_an_empty_set(client, monkeypatch):
|
||||
run_returning(monkeypatch, b"[]")
|
||||
assert client.get_local_libraries() == set()
|
||||
|
||||
|
||||
def test_local_libraries_reports_unreadable_output(client, monkeypatch):
|
||||
run_returning(monkeypatch, b"not json at all")
|
||||
with pytest.raises(DaemonError):
|
||||
client.get_local_libraries()
|
||||
|
||||
|
||||
def test_local_libraries_reports_a_failed_command(client, monkeypatch):
|
||||
run_returning(monkeypatch, b"", returncode=1)
|
||||
with pytest.raises(DaemonError):
|
||||
client.get_local_libraries()
|
||||
Reference in new issue
Block a user