You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ansible/test/units/plugins/shell/test_powershell.py

184 lines
8.4 KiB
Python

from __future__ import annotations
import pytest
from ansible.plugins.shell.powershell import _parse_clixml, _replace_stderr_clixml, ShellModule
CLIXML_WITH_ERROR = b'#< CLIXML\r\n<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">' \
b'<S S="Error">My error</S></Objs>'
def test_replace_stderr_clixml_by_itself():
data = CLIXML_WITH_ERROR
expected = b"My error"
actual = _replace_stderr_clixml(data)
assert actual == expected
def test_replace_stderr_clixml_with_pre_and_post_lines():
data = b"pre\r\n" + CLIXML_WITH_ERROR + b"\r\npost"
expected = b"pre\r\nMy error\r\npost"
actual = _replace_stderr_clixml(data)
assert actual == expected
def test_replace_stderr_clixml_with_remaining_data_on_line():
data = b"pre\r\n" + CLIXML_WITH_ERROR + b"inline\r\npost"
expected = b"pre\r\nMy errorinline\r\npost"
actual = _replace_stderr_clixml(data)
assert actual == expected
def test_replace_stderr_clixml_with_non_utf8_data():
# \x82 in cp437 is é but is an invalid UTF-8 sequence
data = CLIXML_WITH_ERROR.replace(b"error", b"\x82rror")
expected = "My érror".encode("utf-8")
actual = _replace_stderr_clixml(data)
assert actual == expected
def test_replace_stderr_clixml_across_liens():
data = b"#< CLIXML\r\n<Objs Version=\"foo\">\r\n</Objs>"
expected = data
actual = _replace_stderr_clixml(data)
assert actual == expected
def test_replace_stderr_clixml_with_invalid_clixml_data():
data = b"#< CLIXML\r\n<Objs Version=\"foo\"><</Objs>"
expected = data
actual = _replace_stderr_clixml(data)
assert actual == expected
def test_replace_stderr_clixml_with_no_clixml():
data = b"foo"
expected = data
actual = _replace_stderr_clixml(data)
assert actual == expected
def test_replace_stderr_clixml_with_header_but_no_data():
data = b"foo\r\n#< CLIXML\r\n"
expected = data
actual = _replace_stderr_clixml(data)
assert actual == expected
def test_parse_clixml_empty():
empty = b'#< CLIXML\r\n<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"></Objs>'
expected = b''
actual = _parse_clixml(empty)
assert actual == expected
def test_parse_clixml_with_progress():
progress = b'#< CLIXML\r\n<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">' \
b'<Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS>' \
b'<I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil />' \
b'<PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>'
expected = b''
actual = _parse_clixml(progress)
assert actual == expected
def test_parse_clixml_single_stream():
single_stream = b'#< CLIXML\r\n<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">' \
b'<S S="Error">fake : The term \'fake\' is not recognized as the name of a cmdlet. Check _x000D__x000A_</S>' \
b'<S S="Error">the spelling of the name, or if a path was included._x000D__x000A_</S>' \
b'<S S="Error">At line:1 char:1_x000D__x000A_</S>' \
b'<S S="Error">+ fake cmdlet_x000D__x000A_</S><S S="Error">+ ~~~~_x000D__x000A_</S>' \
b'<S S="Error"> + CategoryInfo : ObjectNotFound: (fake:String) [], CommandNotFoundException_x000D__x000A_</S>' \
b'<S S="Error"> + FullyQualifiedErrorId : CommandNotFoundException_x000D__x000A_</S>' \
b'<S S="Error"> _x000D__x000A_</S>' \
b'</Objs>'
expected = b"fake : The term 'fake' is not recognized as the name of a cmdlet. Check \r\n" \
b"the spelling of the name, or if a path was included.\r\n" \
b"At line:1 char:1\r\n" \
b"+ fake cmdlet\r\n" \
b"+ ~~~~\r\n" \
b" + CategoryInfo : ObjectNotFound: (fake:String) [], CommandNotFoundException\r\n" \
b" + FullyQualifiedErrorId : CommandNotFoundException\r\n" \
b" \r\n"
actual = _parse_clixml(single_stream)
assert actual == expected
def test_parse_clixml_multiple_streams():
multiple_stream = b'#< CLIXML\r\n<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">' \
b'<S S="Error">fake : The term \'fake\' is not recognized as the name of a cmdlet. Check _x000D__x000A_</S>' \
b'<S S="Error">the spelling of the name, or if a path was included._x000D__x000A_</S>' \
b'<S S="Error">At line:1 char:1_x000D__x000A_</S>' \
b'<S S="Error">+ fake cmdlet_x000D__x000A_</S><S S="Error">+ ~~~~_x000D__x000A_</S>' \
b'<S S="Error"> + CategoryInfo : ObjectNotFound: (fake:String) [], CommandNotFoundException_x000D__x000A_</S>' \
b'<S S="Error"> + FullyQualifiedErrorId : CommandNotFoundException_x000D__x000A_</S><S S="Error"> _x000D__x000A_</S>' \
b'<S S="Info">hi info</S>' \
b'<S S="Info">other</S>' \
b'</Objs>'
expected = b"hi infoother"
actual = _parse_clixml(multiple_stream, stream="Info")
assert actual == expected
def test_parse_clixml_multiple_elements():
multiple_elements = b'#< CLIXML\r\n#< CLIXML\r\n<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">' \
b'<Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS>' \
b'<I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil />' \
b'<PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj>' \
b'<S S="Error">Error 1</S></Objs>' \
b'<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0">' \
b'<TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS>' \
b'<I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil />' \
b'<PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj>' \
b'<Obj S="progress" RefId="1"><TNRef RefId="0" /><MS><I64 N="SourceId">2</I64>' \
b'<PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil />' \
b'<PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj>' \
b'<S S="Error">Error 2</S></Objs>'
expected = b"Error 1\r\nError 2"
actual = _parse_clixml(multiple_elements)
assert actual == expected
@pytest.mark.parametrize('clixml, expected', [
('', ''),
('just newline _x000A_', 'just newline \n'),
('surrogate pair _xD83C__xDFB5_', 'surrogate pair 🎵'),
('null char _x0000_', 'null char \0'),
('normal char _x0061_', 'normal char a'),
('escaped literal _x005F_x005F_', 'escaped literal _x005F_'),
('underscope before escape _x005F__x000A_', 'underscope before escape _\n'),
('surrogate high _xD83C_', 'surrogate high \uD83C'),
('surrogate low _xDFB5_', 'surrogate low \uDFB5'),
('lower case hex _x005f_', 'lower case hex _'),
('invalid hex _x005G_', 'invalid hex _x005G_'),
# Tests regex actually matches UTF-16-BE hex chars (b"\x00" then hex char).
("_x\u6100\u6200\u6300\u6400_", "_x\u6100\u6200\u6300\u6400_"),
])
def test_parse_clixml_with_comlex_escaped_chars(clixml, expected):
clixml_data = (
'<# CLIXML\r\n'
'<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">'
f'<S S="Error">{clixml}</S>'
'</Objs>'
).encode()
b_expected = expected.encode(errors="surrogatepass")
actual = _parse_clixml(clixml_data)
assert actual == b_expected
def test_join_path_unc():
pwsh = ShellModule()
unc_path_parts = ['\\\\host\\share\\dir1\\\\dir2\\', '\\dir3/dir4', 'dir5', 'dir6\\']
expected = '\\\\host\\share\\dir1\\dir2\\dir3\\dir4\\dir5\\dir6'
actual = pwsh.join_path(*unc_path_parts)
assert actual == expected