canonical / canonical/multipass

[windows][cli_tests] While running CLI tests: 'utf-8' codec can't decode byte 0x92 in position 437: invalid start byte

Closed
#5,240 0 comments 0 reactions 0 assignees View on GitHub
needs triage windows
Dominant language
C++
Stars
9.2k
Forks
828
Avg merge
3d 1h
Merged PRs (30d)
44

Description

**Describe the bug**
Some of the cli_snapshot_test.py (driver=hyperv_api) consistently fail with the following error:
'utf-8' codec can't decode byte 0x92 in position 437: invalid start byte

**To Reproduce**
How, and what happened?
1. Execute: `pytest .\tests\cli\cli_snapshot_test.py --driver=hyperv_api`.
2. Notice how the following tests fail:
- test_take_snapshot_delete_linear_history
- test_take_snapshot_delete_branched
- test_take_snapshot_name_tbd.

**Expected behavior**
The tests should succeed.

**Logs**
```
(.venv) PS C:\Users\TempUser\multipass> pytest --instafail .\tests\cli\cli_snapshot_test.py --driver=hyperv_api
================================================= test session starts =================================================
platform win32 -- Python 3.14.7, pytest-9.1.1, pluggy-1.6.0
rootdir: C:\Users\TempUser\multipass\tests\cli
configfile: pyproject.toml
plugins: instafail-0.5.0
collected 12 items

tests\cli\cli_snapshot_test.py .......F

________________________________ TestSnapshot.test_take_snapshot_delete_linear_history ________________________________

self = , instance = 'balrog-balrog-guard-9372'

def test_take_snapshot_delete_linear_history(self, instance):
"""Confirm that deleting a snapshot in a linear history reduces the
snapshot count appropriately."""

assert snapshot_count(instance) == 0
take_snapshot(instance, "snapshot1")
assert snapshot_count(instance) == 1
take_snapshot(instance, "snapshot2", "snapshot1")
assert snapshot_count(instance) == 2
take_snapshot(instance, "snapshot3", "snapshot2")
assert snapshot_count(instance) == 3

assert multipass("delete", f"{instance}.snapshot1", "--purge")
assert snapshot_count(instance) == 2

> with multipass(
"info", "--format=json", "--snapshots", f"{instance}"
).json() as output:

tests\cli\cli_snapshot_test.py:172:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests\cli\utilities\functional.py:66: in wrapper
result = fn(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^
tests\cli\multipass\multipass_cmd.py:219: in multipass
cmd = RunToCompletion()
^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = .RunToCompletion object at 0x0000014666DCBB60>

def __init__(self):
self.pexpect_child = None
try:
self.pexpect_child = spawn_multipass(
args, timeout=timeout, echo=echo, env=env)
self.pexpect_child.expect(pexpect.EOF, timeout=timeout)
> self.output_text = self.pexpect_child.before.decode("utf-8")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in position 436: invalid start byte

tests\cli\multipass\multipass_cmd.py:194: UnicodeDecodeError
------------------------------------------------ Captured stdout call -------------------------------------------------
'utf-8' codec can't decode byte 0x92 in position 436: invalid start byte
------------------------------------------------ Captured stderr call -------------------------------------------------

❌🔥 Terminating C:\Program Files\Multipass\bin\multipass.EXE info --format=json --snapshots balrog-balrog-guard-9372

tests\cli\cli_snapshot_test.py ..F

___________________________________ TestSnapshot.test_take_snapshot_delete_branched ___________________________________

self = , instance = 'isengard-orc-legion-6275'

@pytest.mark.slow # I mean it.
def test_take_snapshot_delete_branched(self, instance):
"""
Tests snapshot branching and hierarchy preservation in a Multipass VM.

This test launches a VM, creates an initial snapshot, and then repeatedly
restores earlier snapshots to create left and right branches—forming a
binary-style snapshot tree. Each node spawns two child snapshots via
destructive restore + snapshot.
"""
assert snapshot_count(instance) == 0

# Snapshot tree structure to build
snapshot_tree = {
"snapshot1": {
"snapshot2a": {
"snapshot3a": {},
"snapshot3b": {},
},
"snapshot2b": {
"snapshot4a": {},
"snapshot4b": {
"snapshot5a": {},
"snapshot5b": {},
},
},
},
}

build_snapshot_tree(instance, snapshot_tree)
assert snapshot_count(instance) == 9

# Delete the common ancestor
assert multipass("delete", f"{instance}.snapshot1", "--purge")
assert snapshot_count(instance) == 8

assert multipass("restore", f"{instance}.snapshot5b", "--destructive")

# We'll expect all checkpoint files to exist
lineage = find_lineage(snapshot_tree, "snapshot5b")
for ancestor in lineage:
assert path_exists(instance, f"{ancestor}")

assert multipass("stop", instance)

> with multipass(
"info", "--format=json", "--snapshots", instance
).json() as output:

tests\cli\cli_snapshot_test.py:321:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests\cli\utilities\functional.py:66: in wrapper
result = fn(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^
tests\cli\multipass\multipass_cmd.py:219: in multipass
cmd = RunToCompletion()
^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = .RunToCompletion object at 0x0000014666DCBB60>

def __init__(self):
self.pexpect_child = None
try:
self.pexpect_child = spawn_multipass(
args, timeout=timeout, echo=echo, env=env)
self.pexpect_child.expect(pexpect.EOF, timeout=timeout)
> self.output_text = self.pexpect_child.before.decode("utf-8")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in position 437: invalid start byte

tests\cli\multipass\multipass_cmd.py:194: UnicodeDecodeError
------------------------------------------------ Captured stdout call -------------------------------------------------
'utf-8' codec can't decode byte 0x92 in position 437: invalid start byte
------------------------------------------------ Captured stderr call -------------------------------------------------

❌🔥 Terminating C:\Program Files\Multipass\bin\multipass.EXE info --format=json --snapshots isengard-orc-legion-6275

tests\cli\cli_snapshot_test.py F

______________________________________ TestSnapshot.test_take_snapshot_name_tbd _______________________________________

self = , instance = 'mithril-eomer-posse-0696'

@pytest.mark.slow # I mean it.
def test_take_snapshot_name_tbd(self, instance):
"""
Test snapshot forward merging.

Given snapshot a -> b -> c, and snapshot "b" being deleted, snapshot b
should be merged into snapshot "c", and its other children, if any. The
test confirmst that it is the case.
"""
assert snapshot_count(instance) == 0

# Snapshot tree structure to build
snapshot_tree = {
"snapshot0": {
"snapshot1": {
"snapshot2": {
"snapshot3": {},
"snapshot2b": {}
},
"snapshot1b": {}
}
}
}

build_snapshot_tree(instance, snapshot_tree)
assert snapshot_count(instance) == 6

# Delete snapshot 2 so snapshot3 and 2b would be orphaned. We expect
# snapshot 3 and 2b contain snapshot2's delta, and snapshot1 unaltered.
assert multipass("delete", f"{instance}.snapshot2", "--purge")

> with multipass(
"info", "--format=json", "--snapshots", f"{instance}"
).json() as output:

tests\cli\cli_snapshot_test.py:425:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests\cli\utilities\functional.py:66: in wrapper
result = fn(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^
tests\cli\multipass\multipass_cmd.py:219: in multipass
cmd = RunToCompletion()
^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = .RunToCompletion object at 0x0000014666EB86E0>

def __init__(self):
self.pexpect_child = None
try:
self.pexpect_child = spawn_multipass(
args, timeout=timeout, echo=echo, env=env)
self.pexpect_child.expect(pexpect.EOF, timeout=timeout)
> self.output_text = self.pexpect_child.before.decode("utf-8")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in position 436: invalid start byte

tests\cli\multipass\multipass_cmd.py:194: UnicodeDecodeError
------------------------------------------------ Captured stdout call -------------------------------------------------
'utf-8' codec can't decode byte 0x92 in position 436: invalid start byte
------------------------------------------------ Captured stderr call -------------------------------------------------

❌🔥 Terminating C:\Program Files\Multipass\bin\multipass.EXE info --format=json --snapshots mithril-eomer-posse-0696
[100%]
=============================================== short test summary info ===============================================
FAILED tests\cli\cli_snapshot_test.py::TestSnapshot::test_take_snapshot_delete_linear_history - UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in position 436: invalid start byte
FAILED tests\cli\cli_snapshot_test.py::TestSnapshot::test_take_snapshot_delete_branched - UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in position 437: invalid start byte
FAILED tests\cli\cli_snapshot_test.py::TestSnapshot::test_take_snapshot_name_tbd - UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in position 436: invalid start byte
====================================== 3 failed, 9 passed in 1542.61s (0:25:42) =======================================
```

**Additional info**
- OS: Win 11 Pro
- CPU architecture or model: x64
- `multipass version`
1.17.0-dev.ci6331+ga58dcf2e.win

**Additional context**
Add any other context about the problem here.

Contributor guide

Open the contributing guide

Research direction

Run pytest .\tests\cli\cli_snapshot_test.py --driver=hyperv_api on Windows, then inspect tests/cli/multipass/multipass_cmd.py around RunToCompletion and the decode call. Compare the failing snapshot tests in tests/cli/cli_snapshot_test.py with passing CLI tests and verify that all 12 tests complete without UnicodeDecodeError.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli, operating-systems, testing
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.