mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-07 05:42:50 +00:00
win_exec: refactor PS exec runner (#45334)
* win_exec: refactor PS exec runner * more changes for PSCore compatibility * made some changes based on the recent review * split up module exec scripts for smaller payload * removed C# module support to focus on just error msg improvement * cleaned up c# test classifier code
This commit is contained in:
committed by
Matt Davis
parent
aa2f3edb49
commit
e972287c35
@@ -148,8 +148,7 @@
|
||||
- asyncresult is finished
|
||||
- asyncresult is not changed
|
||||
- asyncresult is failed
|
||||
# TODO: re-enable after catastrophic failure behavior is cleaned up
|
||||
# - asyncresult.msg is search('failing via exception')
|
||||
- 'asyncresult.msg == "Unhandled exception while executing module: failing via exception"'
|
||||
|
||||
- name: echo some non ascii characters
|
||||
win_command: cmd.exe /c echo über den Fußgängerübergang gehen
|
||||
|
||||
@@ -136,7 +136,8 @@
|
||||
register: become_invalid_pass
|
||||
failed_when:
|
||||
- '"Failed to become user " + become_test_username not in become_invalid_pass.msg'
|
||||
- '"LogonUser failed (The user name or password is incorrect, Win32ErrorCode 1326)" not in become_invalid_pass.msg'
|
||||
- '"LogonUser failed" not in become_invalid_pass.msg'
|
||||
- '"Win32ErrorCode 1326)" not in become_invalid_pass.msg'
|
||||
|
||||
- name: test become with SYSTEM account
|
||||
win_whoami:
|
||||
@@ -206,21 +207,21 @@
|
||||
become_flags: logon_type=batch invalid_flags=a
|
||||
become_method: runas
|
||||
register: failed_flags_invalid_key
|
||||
failed_when: "failed_flags_invalid_key.msg != \"Failed to parse become_flags 'logon_type=batch invalid_flags=a': become_flags key 'invalid_flags' is not a valid runas flag, must be 'logon_type' or 'logon_flags'\""
|
||||
failed_when: "failed_flags_invalid_key.msg != \"internal error: failed to parse become_flags 'logon_type=batch invalid_flags=a': become_flags key 'invalid_flags' is not a valid runas flag, must be 'logon_type' or 'logon_flags'\""
|
||||
|
||||
- name: test failure with invalid logon_type
|
||||
vars: *become_vars
|
||||
win_whoami:
|
||||
become_flags: logon_type=invalid
|
||||
register: failed_flags_invalid_type
|
||||
failed_when: "failed_flags_invalid_type.msg != \"Failed to parse become_flags 'logon_type=invalid': become_flags logon_type value 'invalid' is not valid, valid values are: interactive, network, batch, service, unlock, network_cleartext, new_credentials\""
|
||||
failed_when: "failed_flags_invalid_type.msg != \"internal error: failed to parse become_flags 'logon_type=invalid': become_flags logon_type value 'invalid' is not valid, valid values are: interactive, network, batch, service, unlock, network_cleartext, new_credentials\""
|
||||
|
||||
- name: test failure with invalid logon_flag
|
||||
vars: *become_vars
|
||||
win_whoami:
|
||||
become_flags: logon_flags=with_profile,invalid
|
||||
register: failed_flags_invalid_flag
|
||||
failed_when: "failed_flags_invalid_flag.msg != \"Failed to parse become_flags 'logon_flags=with_profile,invalid': become_flags logon_flags value 'invalid' is not valid, valid values are: with_profile, netcredentials_only\""
|
||||
failed_when: "failed_flags_invalid_flag.msg != \"internal error: failed to parse become_flags 'logon_flags=with_profile,invalid': become_flags logon_flags value 'invalid' is not valid, valid values are: with_profile, netcredentials_only\""
|
||||
|
||||
# Server 2008 doesn't work with network and network_cleartext, there isn't really a reason why you would want this anyway
|
||||
- name: check if we are running on a dinosaur, neanderthal or an OS of the modern age
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#!powershell
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Function Assert-Equals($actual, $expected) {
|
||||
if ($actual -cne $expected) {
|
||||
$call_stack = (Get-PSCallStack)[1]
|
||||
$error_msg = "AssertionError:`r`nActual: `"$actual`" != Expected: `"$expected`"`r`nLine: $($call_stack.ScriptLineNumber), Method: $($call_stack.Position.Text)"
|
||||
Fail-Json -obj $result -message $error_msg
|
||||
}
|
||||
}
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
|
||||
#ConvertFrom-AnsibleJso
|
||||
$input_json = '{"string":"string","float":3.1415926,"dict":{"string":"string","int":1},"list":["entry 1","entry 2"],"null":null,"int":1}'
|
||||
$actual = ConvertFrom-AnsibleJson -InputObject $input_json
|
||||
Assert-Equals -actual $actual.GetType() -expected ([Hashtable])
|
||||
Assert-Equals -actual $actual.string.GetType() -expected ([String])
|
||||
Assert-Equals -actual $actual.string -expected "string"
|
||||
Assert-Equals -actual $actual.int.GetType() -expected ([Int32])
|
||||
Assert-Equals -actual $actual.int -expected 1
|
||||
Assert-Equals -actual $actual.null -expected $null
|
||||
Assert-Equals -actual $actual.float.GetType() -expected ([Decimal])
|
||||
Assert-Equals -actual $actual.float -expected 3.1415926
|
||||
Assert-Equals -actual $actual.list.GetType() -expected ([Object[]])
|
||||
Assert-Equals -actual $actual.list.Count -expected 2
|
||||
Assert-Equals -actual $actual.list[0] -expected "entry 1"
|
||||
Assert-Equals -actual $actual.list[1] -expected "entry 2"
|
||||
Assert-Equals -actual $actual.GetType() -expected ([Hashtable])
|
||||
Assert-Equals -actual $actual.dict.string -expected "string"
|
||||
Assert-Equals -actual $actual.dict.int -expected 1
|
||||
|
||||
$result.msg = "good"
|
||||
Exit-Json -obj $result
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#!powershell
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
|
||||
$data = Get-AnsibleParam -obj $params -name "data" -type "str" -default "normal"
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
|
||||
<#
|
||||
This module tests various error events in PowerShell to verify our hidden trap
|
||||
catches them all and outputs a pretty error message with a traceback to help
|
||||
users debug the actual issue
|
||||
|
||||
normal - normal execution, no errors
|
||||
fail - Calls Fail-Json like normal
|
||||
throw - throws an exception
|
||||
error - Write-Error with ErrorActionPreferenceStop
|
||||
cmdlet_error - Calls a Cmdlet with an invalid error
|
||||
dotnet_exception - Calls a .NET function that will throw an error
|
||||
function_throw - Throws an exception in a function
|
||||
proc_exit_fine - calls an executable with a non-zero exit code with Exit-Json
|
||||
proc_exit_fail - calls an executable with a non-zero exit code with Fail-Json
|
||||
#>
|
||||
|
||||
Function Test-ThrowException {
|
||||
throw "exception in function"
|
||||
}
|
||||
|
||||
if ($data -eq "normal") {
|
||||
Exit-Json -obj $result
|
||||
} elseif ($data -eq "fail") {
|
||||
Fail-Json -obj $result -message "fail message"
|
||||
} elseif ($data -eq "throw") {
|
||||
throw [ArgumentException]"module is thrown"
|
||||
} elseif ($data -eq "error") {
|
||||
Write-Error -Message $data
|
||||
} elseif ($data -eq "cmdlet_error") {
|
||||
Get-Item -Path "fake:\path"
|
||||
} elseif ($data -eq "dotnet_exception") {
|
||||
[System.IO.Path]::GetFullPath($null)
|
||||
} elseif ($data -eq "function_throw") {
|
||||
Test-ThrowException
|
||||
} elseif ($data -eq "proc_exit_fine") {
|
||||
# verifies that if no error was actually fired and we have an output, we
|
||||
# don't use the RC to validate if the module failed
|
||||
&cmd.exe /c exit 2
|
||||
Exit-Json -obj $result
|
||||
} elseif ($data -eq "proc_exit_fail") {
|
||||
&cmd.exe /c exit 2
|
||||
Fail-Json -obj $result -message "proc_exit_fail"
|
||||
}
|
||||
|
||||
# verify no exception were silently caught during our tests
|
||||
Fail-Json -obj $result -message "end of module"
|
||||
|
||||
@@ -1,4 +1,115 @@
|
||||
---
|
||||
- name: test normal module execution
|
||||
test_fail:
|
||||
register: normal
|
||||
|
||||
- name: assert test normal module execution
|
||||
assert:
|
||||
that:
|
||||
- not normal is failed
|
||||
|
||||
- name: test fail module execution
|
||||
test_fail:
|
||||
data: fail
|
||||
register: fail_module
|
||||
ignore_errors: yes
|
||||
|
||||
- name: assert test fail module execution
|
||||
assert:
|
||||
that:
|
||||
- fail_module is failed
|
||||
- fail_module.msg == "fail message"
|
||||
- not fail_module.exception is defined
|
||||
|
||||
- name: test module with exception thrown
|
||||
test_fail:
|
||||
data: throw
|
||||
register: throw_module
|
||||
ignore_errors: yes
|
||||
|
||||
- name: assert test module with exception thrown
|
||||
assert:
|
||||
that:
|
||||
- throw_module is failed
|
||||
- 'throw_module.msg == "Unhandled exception while executing module: module is thrown"'
|
||||
- '"throw [ArgumentException]\"module is thrown\"" in throw_module.exception'
|
||||
|
||||
- name: test module with error msg
|
||||
test_fail:
|
||||
data: error
|
||||
register: error_module
|
||||
ignore_errors: yes
|
||||
|
||||
- name: assert test module with error msg
|
||||
assert:
|
||||
that:
|
||||
- error_module is failed
|
||||
- 'error_module.msg == "Unhandled exception while executing module: error"'
|
||||
- '"Write-Error -Message $data" in error_module.exception'
|
||||
|
||||
- name: test module with cmdlet error
|
||||
test_fail:
|
||||
data: cmdlet_error
|
||||
register: cmdlet_error
|
||||
ignore_errors: yes
|
||||
|
||||
- name: assert test module with cmdlet error
|
||||
assert:
|
||||
that:
|
||||
- cmdlet_error is failed
|
||||
- 'cmdlet_error.msg == "Unhandled exception while executing module: Cannot find drive. A drive with the name ''fake'' does not exist."'
|
||||
- '"Get-Item -Path \"fake:\\path\"" in cmdlet_error.exception'
|
||||
|
||||
- name: test module with .NET exception
|
||||
test_fail:
|
||||
data: dotnet_exception
|
||||
register: dotnet_exception
|
||||
ignore_errors: yes
|
||||
|
||||
- name: assert test module with .NET exception
|
||||
assert:
|
||||
that:
|
||||
- dotnet_exception is failed
|
||||
- 'dotnet_exception.msg == "Unhandled exception while executing module: Exception calling \"GetFullPath\" with \"1\" argument(s): \"The path is not of a legal form.\""'
|
||||
- '"[System.IO.Path]::GetFullPath($null)" in dotnet_exception.exception'
|
||||
|
||||
- name: test module with function exception
|
||||
test_fail:
|
||||
data: function_throw
|
||||
register: function_exception
|
||||
ignore_errors: yes
|
||||
|
||||
- name: assert test module with function exception
|
||||
assert:
|
||||
that:
|
||||
- function_exception is failed
|
||||
- 'function_exception.msg == "Unhandled exception while executing module: exception in function"'
|
||||
- '"throw \"exception in function\"" in function_exception.exception'
|
||||
- '"at Test-ThrowException, <No file>: line" in function_exception.exception'
|
||||
|
||||
- name: test module with fail process but Exit-Json
|
||||
test_fail:
|
||||
data: proc_exit_fine
|
||||
register: proc_exit_fine
|
||||
|
||||
- name: assert test module with fail process but Exit-Json
|
||||
assert:
|
||||
that:
|
||||
- not proc_exit_fine is failed
|
||||
|
||||
- name: test module with fail process but Fail-Json
|
||||
test_fail:
|
||||
data: proc_exit_fail
|
||||
register: proc_exit_fail
|
||||
ignore_errors: yes
|
||||
|
||||
- name: assert test module with fail process but Fail-Json
|
||||
assert:
|
||||
that:
|
||||
- proc_exit_fail is failed
|
||||
- proc_exit_fail.msg == "proc_exit_fail"
|
||||
- not proc_exit_fail.exception is defined
|
||||
|
||||
- name: test out invalid options
|
||||
test_invalid_requires:
|
||||
register: invalid_options
|
||||
@@ -127,3 +238,13 @@
|
||||
args:
|
||||
executable: cmd.exe
|
||||
when: become_test_username in profile_dir_out.stdout_lines[0]
|
||||
|
||||
- name: test common functions in exec
|
||||
test_common_functions:
|
||||
register: common_functions_res
|
||||
|
||||
- name: assert test common functions in exec
|
||||
assert:
|
||||
that:
|
||||
- not common_functions_res is failed
|
||||
- common_functions_res.msg == "good"
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
#!powershell
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
}
|
||||
|
||||
Function Assert-Equals($actual, $expected) {
|
||||
if ($actual -cne $expected) {
|
||||
$call_stack = (Get-PSCallStack)[1]
|
||||
$error_msg = "AssertionError:`r`nActual: `"$actual`" != Expected: `"$expected`"`r`nLine: $($call_stack.ScriptLineNumber), Method: $($call_stack.Position.Text)"
|
||||
Fail-Json -obj $result -message $error_msg
|
||||
}
|
||||
}
|
||||
|
||||
$code = @'
|
||||
using System;
|
||||
|
||||
namespace Namespace1
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
public static string GetString(bool error)
|
||||
{
|
||||
if (error)
|
||||
throw new Exception("error");
|
||||
return "Hello World";
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
$res = Add-CSharpType -References $code
|
||||
Assert-Equals -actual $res -expected $null
|
||||
|
||||
$actual = [Namespace1.Class1]::GetString($false)
|
||||
Assert-Equals $actual -expected "Hello World"
|
||||
|
||||
try {
|
||||
[Namespace1.Class1]::GetString($true)
|
||||
} catch {
|
||||
Assert-Equals ($_.Exception.ToString().Contains("at Namespace1.Class1.GetString(Boolean error)`r`n")) -expected $true
|
||||
}
|
||||
|
||||
$code_debug = @'
|
||||
using System;
|
||||
|
||||
namespace Namespace2
|
||||
{
|
||||
public class Class2
|
||||
{
|
||||
public static string GetString(bool error)
|
||||
{
|
||||
if (error)
|
||||
throw new Exception("error");
|
||||
return "Hello World";
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
$res = Add-CSharpType -References $code_debug -IncludeDebugInfo
|
||||
Assert-Equals -actual $res -expected $null
|
||||
|
||||
$actual = [Namespace2.Class2]::GetString($false)
|
||||
Assert-Equals $actual -expected "Hello World"
|
||||
|
||||
try {
|
||||
[Namespace2.Class2]::GetString($true)
|
||||
} catch {
|
||||
$tmp_path = [System.IO.Path]::GetFullPath($env:TMP).ToLower()
|
||||
Assert-Equals ($_.Exception.ToString().ToLower().Contains("at namespace2.class2.getstring(boolean error) in $tmp_path")) -expected $true
|
||||
Assert-Equals ($_.Exception.ToString().Contains(".cs:line 10")) -expected $true
|
||||
}
|
||||
|
||||
$code_tmp = @'
|
||||
using System;
|
||||
|
||||
namespace Namespace3
|
||||
{
|
||||
public class Class3
|
||||
{
|
||||
public static string GetString(bool error)
|
||||
{
|
||||
if (error)
|
||||
throw new Exception("error");
|
||||
return "Hello World";
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
$tmp_path = $env:USERPROFILE
|
||||
$res = Add-CSharpType -References $code_tmp -IncludeDebugInfo -TempPath $tmp_path -PassThru
|
||||
Assert-Equals -actual $res.GetType().Name -expected "RuntimeAssembly"
|
||||
Assert-Equals -actual $res.Location -expected ""
|
||||
Assert-Equals -actual $res.GetTypes().Length -expected 1
|
||||
Assert-Equals -actual $res.GetTypes()[0].Name -expected "Class3"
|
||||
|
||||
$actual = [Namespace3.Class3]::GetString($false)
|
||||
Assert-Equals $actual -expected "Hello World"
|
||||
|
||||
try {
|
||||
[Namespace3.Class3]::GetString($true)
|
||||
} catch {
|
||||
Assert-Equals ($_.Exception.ToString().ToLower().Contains("at namespace3.class3.getstring(boolean error) in $($tmp_path.ToLower())")) -expected $true
|
||||
Assert-Equals ($_.Exception.ToString().Contains(".cs:line 10")) -expected $true
|
||||
}
|
||||
|
||||
$warning_code = @'
|
||||
using System;
|
||||
|
||||
namespace Namespace4
|
||||
{
|
||||
public class Class4
|
||||
{
|
||||
public static string GetString(bool test)
|
||||
{
|
||||
if (test)
|
||||
{
|
||||
string a = "";
|
||||
}
|
||||
|
||||
return "Hello World";
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
$failed = $false
|
||||
try {
|
||||
Add-CSharpType -References $warning_code
|
||||
} catch {
|
||||
$failed = $true
|
||||
Assert-Equals -actual ($_.Exception.Message.Contains("error CS0219: Warning as Error: The variable 'a' is assigned but its value is never used")) -expected $true
|
||||
}
|
||||
Assert-Equals -actual $failed -expected $true
|
||||
|
||||
Add-CSharpType -References $warning_code -IgnoreWarnings
|
||||
$actual = [Namespace4.Class4]::GetString($true)
|
||||
Assert-Equals -actual $actual -expected "Hello World"
|
||||
|
||||
$reference_1 = @'
|
||||
using System;
|
||||
using System.Web.Script.Serialization;
|
||||
|
||||
//AssemblyReference -Name System.Web.Extensions.dll
|
||||
|
||||
namespace Namespace5
|
||||
{
|
||||
public class Class5
|
||||
{
|
||||
public static string GetString()
|
||||
{
|
||||
return "Hello World";
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
$reference_2 = @'
|
||||
using System;
|
||||
using Namespace5;
|
||||
using System.Management.Automation;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Namespace6
|
||||
{
|
||||
public class Class6
|
||||
{
|
||||
public static string GetString()
|
||||
{
|
||||
Hashtable hash = new Hashtable();
|
||||
hash["test"] = "abc";
|
||||
return Class5.GetString();
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
Add-CSharpType -References $reference_1, $reference_2
|
||||
$actual = [Namespace6.Class6]::GetString()
|
||||
Assert-Equals -actual $actual -expected "Hello World"
|
||||
|
||||
$result.res = "success"
|
||||
Exit-Json -obj $result
|
||||
@@ -0,0 +1,12 @@
|
||||
#1powershell
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#AnsibleRequires -CSharpUtil Ansible.Test
|
||||
|
||||
$result = @{
|
||||
res = [Ansible.Test.OutputTest]::GetString()
|
||||
changed = $false
|
||||
}
|
||||
|
||||
Exit-Json -obj $result
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//AssemblyReference -Name System.Web.Extensions.dll
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Script.Serialization;
|
||||
|
||||
namespace Ansible.Test
|
||||
{
|
||||
public class OutputTest
|
||||
{
|
||||
public static string GetString()
|
||||
{
|
||||
Dictionary<string, object> obj = new Dictionary<string, object>();
|
||||
obj["a"] = "a";
|
||||
obj["b"] = 1;
|
||||
return ToJson(obj);
|
||||
}
|
||||
|
||||
private static string ToJson(object obj)
|
||||
{
|
||||
JavaScriptSerializer jss = new JavaScriptSerializer();
|
||||
return jss.Serialize(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,3 +143,23 @@
|
||||
- assert:
|
||||
that:
|
||||
- privilege_util_test.data == 'success'
|
||||
|
||||
- name: call module with C# reference
|
||||
csharp_util:
|
||||
register: csharp_res
|
||||
|
||||
- name: assert call module with C# reference
|
||||
assert:
|
||||
that:
|
||||
- not csharp_res is failed
|
||||
- csharp_res.res == '{"a":"a","b":1}'
|
||||
|
||||
- name: call module with AddType tests
|
||||
add_type_test:
|
||||
register: add_type_test
|
||||
|
||||
- name: assert call module with AddType tests
|
||||
assert:
|
||||
that:
|
||||
- not add_type_test is failed
|
||||
- add_type_test.res == 'success'
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
- win_ping_ps1_result is not changed
|
||||
- win_ping_ps1_result.ping == 'bleep'
|
||||
|
||||
# TODO: this will have to be removed once PS basic is implemented
|
||||
- name: test win_ping with extra args to verify that v2 module replacer escaping works as expected
|
||||
win_ping:
|
||||
data: bloop
|
||||
@@ -92,71 +93,5 @@
|
||||
that:
|
||||
- win_ping_crash_result is failed
|
||||
- win_ping_crash_result is not changed
|
||||
- "'FullyQualifiedErrorId : boom' in win_ping_crash_result.module_stderr"
|
||||
|
||||
# TODO: fix code or tests? discrete error returns from PS are strange...
|
||||
|
||||
#- name: test modified win_ping that throws an exception
|
||||
# action: win_ping_throw
|
||||
# register: win_ping_throw_result
|
||||
# ignore_errors: true
|
||||
#
|
||||
#- name: check win_ping_throw result
|
||||
# assert:
|
||||
# that:
|
||||
# - win_ping_throw_result is failed
|
||||
# - win_ping_throw_result is not changed
|
||||
# - win_ping_throw_result.msg == 'MODULE FAILURE'
|
||||
# - win_ping_throw_result.exception
|
||||
# - win_ping_throw_result.error_record
|
||||
#
|
||||
#- name: test modified win_ping that throws a string exception
|
||||
# action: win_ping_throw_string
|
||||
# register: win_ping_throw_string_result
|
||||
# ignore_errors: true
|
||||
#
|
||||
#- name: check win_ping_throw_string result
|
||||
# assert:
|
||||
# that:
|
||||
# - win_ping_throw_string_result is failed
|
||||
# - win_ping_throw_string_result is not changed
|
||||
# - win_ping_throw_string_result.msg == 'no ping for you'
|
||||
# - win_ping_throw_string_result.exception
|
||||
# - win_ping_throw_string_result.error_record
|
||||
#
|
||||
#- name: test modified win_ping that has a syntax error
|
||||
# action: win_ping_syntax_error
|
||||
# register: win_ping_syntax_error_result
|
||||
# ignore_errors: true
|
||||
#
|
||||
#- name: check win_ping_syntax_error result
|
||||
# assert:
|
||||
# that:
|
||||
# - win_ping_syntax_error_result is failed
|
||||
# - win_ping_syntax_error_result is not changed
|
||||
# - win_ping_syntax_error_result.msg
|
||||
# - win_ping_syntax_error_result.exception
|
||||
#
|
||||
#- name: test modified win_ping that has an error that only surfaces when strict mode is on
|
||||
# action: win_ping_strict_mode_error
|
||||
# register: win_ping_strict_mode_error_result
|
||||
# ignore_errors: true
|
||||
#
|
||||
#- name: check win_ping_strict_mode_error result
|
||||
# assert:
|
||||
# that:
|
||||
# - win_ping_strict_mode_error_result is failed
|
||||
# - win_ping_strict_mode_error_result is not changed
|
||||
# - win_ping_strict_mode_error_result.msg
|
||||
# - win_ping_strict_mode_error_result.exception
|
||||
#
|
||||
#- name: test modified win_ping to verify a Set-Attr fix
|
||||
# action: win_ping_set_attr data="fixed"
|
||||
# register: win_ping_set_attr_result
|
||||
#
|
||||
#- name: check win_ping_set_attr_result result
|
||||
# assert:
|
||||
# that:
|
||||
# - win_ping_set_attr_result is not failed
|
||||
# - win_ping_set_attr_result is not changed
|
||||
# - win_ping_set_attr_result.ping == 'fixed'
|
||||
- 'win_ping_crash_result.msg == "Unhandled exception while executing module: boom"'
|
||||
- '"throw \"boom\"" in win_ping_crash_result.exception'
|
||||
|
||||
@@ -25,6 +25,10 @@ from lib.import_analysis import (
|
||||
get_python_module_utils_imports,
|
||||
)
|
||||
|
||||
from lib.csharp_import_analysis import (
|
||||
get_csharp_module_utils_imports,
|
||||
)
|
||||
|
||||
from lib.powershell_import_analysis import (
|
||||
get_powershell_module_utils_imports,
|
||||
)
|
||||
@@ -168,6 +172,7 @@ class PathMapper(object):
|
||||
self.units_targets = list(walk_units_targets())
|
||||
self.sanity_targets = list(walk_sanity_targets())
|
||||
self.powershell_targets = [t for t in self.sanity_targets if os.path.splitext(t.path)[1] == '.ps1']
|
||||
self.csharp_targets = [t for t in self.sanity_targets if os.path.splitext(t.path)[1] == '.cs']
|
||||
|
||||
self.units_modules = set(t.module for t in self.units_targets if t.module)
|
||||
self.units_paths = set(a for t in self.units_targets for a in t.aliases)
|
||||
@@ -189,6 +194,7 @@ class PathMapper(object):
|
||||
|
||||
self.python_module_utils_imports = {} # populated on first use to reduce overhead when not needed
|
||||
self.powershell_module_utils_imports = {} # populated on first use to reduce overhead when not needed
|
||||
self.csharp_module_utils_imports = {} # populated on first use to reduce overhead when not needed
|
||||
|
||||
def get_dependent_paths(self, path):
|
||||
"""
|
||||
@@ -204,6 +210,9 @@ class PathMapper(object):
|
||||
if ext == '.psm1':
|
||||
return self.get_powershell_module_utils_usage(path)
|
||||
|
||||
if ext == '.cs':
|
||||
return self.get_csharp_module_utils_usage(path)
|
||||
|
||||
if path.startswith('test/integration/targets/'):
|
||||
return self.get_integration_target_usage(path)
|
||||
|
||||
@@ -247,6 +256,22 @@ class PathMapper(object):
|
||||
|
||||
return sorted(self.powershell_module_utils_imports[name])
|
||||
|
||||
def get_csharp_module_utils_usage(self, path):
|
||||
"""
|
||||
:type path: str
|
||||
:rtype: list[str]
|
||||
"""
|
||||
if not self.csharp_module_utils_imports:
|
||||
display.info('Analyzing C# module_utils imports...')
|
||||
before = time.time()
|
||||
self.csharp_module_utils_imports = get_csharp_module_utils_imports(self.powershell_targets, self.csharp_targets)
|
||||
after = time.time()
|
||||
display.info('Processed %d C# module_utils in %d second(s).' % (len(self.csharp_module_utils_imports), after - before))
|
||||
|
||||
name = os.path.splitext(os.path.basename(path))[0]
|
||||
|
||||
return sorted(self.csharp_module_utils_imports[name])
|
||||
|
||||
def get_integration_target_usage(self, path):
|
||||
"""
|
||||
:type path: str
|
||||
@@ -320,7 +345,7 @@ class PathMapper(object):
|
||||
return {
|
||||
'units': module_name if module_name in self.units_modules else None,
|
||||
'integration': self.posix_integration_by_module.get(module_name) if ext == '.py' else None,
|
||||
'windows-integration': self.windows_integration_by_module.get(module_name) if ext == '.ps1' else None,
|
||||
'windows-integration': self.windows_integration_by_module.get(module_name) if ext in ['.cs', '.ps1'] else None,
|
||||
'network-integration': self.network_integration_by_module.get(module_name),
|
||||
FOCUSED_TARGET: True,
|
||||
}
|
||||
@@ -328,6 +353,9 @@ class PathMapper(object):
|
||||
return minimal
|
||||
|
||||
if path.startswith('lib/ansible/module_utils/'):
|
||||
if ext == '.cs':
|
||||
return minimal # already expanded using get_dependent_paths
|
||||
|
||||
if ext == '.psm1':
|
||||
return minimal # already expanded using get_dependent_paths
|
||||
|
||||
|
||||
76
test/runner/lib/csharp_import_analysis.py
Normal file
76
test/runner/lib/csharp_import_analysis.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""Analyze C# import statements."""
|
||||
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from lib.util import (
|
||||
display,
|
||||
)
|
||||
|
||||
|
||||
def get_csharp_module_utils_imports(powershell_targets, csharp_targets):
|
||||
"""Return a dictionary of module_utils names mapped to sets of powershell file paths.
|
||||
:type powershell_targets: list[TestTarget] - C# files
|
||||
:type csharp_targets: list[TestTarget] - PS files
|
||||
:rtype: dict[str, set[str]]
|
||||
"""
|
||||
|
||||
module_utils = enumerate_module_utils()
|
||||
|
||||
imports_by_target_path = {}
|
||||
|
||||
for target in powershell_targets:
|
||||
imports_by_target_path[target.path] = extract_csharp_module_utils_imports(target.path, module_utils, False)
|
||||
|
||||
for target in csharp_targets:
|
||||
imports_by_target_path[target.path] = extract_csharp_module_utils_imports(target.path, module_utils, True)
|
||||
|
||||
imports = dict([(module_util, set()) for module_util in module_utils])
|
||||
|
||||
for target_path in imports_by_target_path:
|
||||
for module_util in imports_by_target_path[target_path]:
|
||||
imports[module_util].add(target_path)
|
||||
|
||||
for module_util in sorted(imports):
|
||||
if not imports[module_util]:
|
||||
display.warning('No imports found which use the "%s" module_util.' % module_util)
|
||||
|
||||
return imports
|
||||
|
||||
|
||||
def enumerate_module_utils():
|
||||
"""Return a list of available module_utils imports.
|
||||
:rtype: set[str]
|
||||
"""
|
||||
return set(os.path.splitext(p)[0] for p in os.listdir('lib/ansible/module_utils/csharp') if os.path.splitext(p)[1] == '.cs')
|
||||
|
||||
|
||||
def extract_csharp_module_utils_imports(path, module_utils, is_pure_csharp):
|
||||
"""Return a list of module_utils imports found in the specified source file.
|
||||
:type path: str
|
||||
:type module_utils: set[str]
|
||||
:rtype: set[str]
|
||||
"""
|
||||
imports = set()
|
||||
if is_pure_csharp:
|
||||
pattern = re.compile(r'(?i)^using\s(Ansible\..+);$')
|
||||
else:
|
||||
pattern = re.compile(r'(?i)^#\s*ansiblerequires\s+-csharputil\s+(Ansible\..+)')
|
||||
|
||||
with open(path, 'r') as module_file:
|
||||
for line_number, line in enumerate(module_file, 1):
|
||||
match = re.search(pattern, line)
|
||||
|
||||
if not match:
|
||||
continue
|
||||
|
||||
import_name = match.group(1)
|
||||
if import_name not in module_utils:
|
||||
display.warning('%s:%d Invalid module_utils import: %s' % (path, line_number, import_name))
|
||||
continue
|
||||
|
||||
imports.add(import_name)
|
||||
|
||||
return imports
|
||||
@@ -489,6 +489,7 @@ def is_binary_file(path):
|
||||
'.cfg',
|
||||
'.conf',
|
||||
'.crt',
|
||||
'.cs',
|
||||
'.css',
|
||||
'.html',
|
||||
'.ini',
|
||||
|
||||
Reference in New Issue
Block a user