This commit is contained in:
Bernie White 2024-06-08 16:21:37 +10:00 коммит произвёл GitHub
Родитель 29e96c8c63
Коммит 3b6be55376
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
30 изменённых файлов: 832 добавлений и 549 удалений

Просмотреть файл

@ -292,6 +292,7 @@ The following conceptual topics exist in the `PSRule` module:
- [Configuration](https://aka.ms/ps-rule/options#configuration)
- [Convention.Include](https://aka.ms/ps-rule/options#conventioninclude)
- [Execution.AliasReference](https://aka.ms/ps-rule/options#executionaliasreference)
- [Execution.Break](https://aka.ms/ps-rule/options#executionbreak)
- [Execution.DuplicateResourceId](https://aka.ms/ps-rule/options#executionduplicateresourceid)
- [Execution.HashAlgorithm](https://aka.ms/ps-rule/options#executionhashalgorithm)
- [Execution.LanguageMode](https://aka.ms/ps-rule/options#executionlanguagemode)
@ -303,6 +304,7 @@ The following conceptual topics exist in the `PSRule` module:
- [Execution.UnprocessedObject](https://aka.ms/ps-rule/options#executionunprocessedobject)
- [Include.Module](https://aka.ms/ps-rule/options#includemodule)
- [Include.Path](https://aka.ms/ps-rule/options#includepath)
- [Input.FileObjects](https://aka.ms/ps-rule/options#inputfileobjects)
- [Input.Format](https://aka.ms/ps-rule/options#inputformat)
- [Input.IgnoreGitPath](https://aka.ms/ps-rule/options#inputignoregitpath)
- [Input.IgnoreObjectSource](https://aka.ms/ps-rule/options#inputignoreobjectsource)

Просмотреть файл

@ -29,6 +29,13 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
What's changed since pre-release v3.0.0-B0203:
- New features:
- Added option to configure the severity level that PSRule will break the pipeline at by @BernieWhite.
[#1508](https://github.com/microsoft/PSRule/issues/1508)
- Previously only rules with the severity level `Error` would break the pipeline.
- With this update rules with the severity level `Error` that fail will break the pipeline by default.
- The `Execution.Break` option can be set to `Never`, `OnError`, `OnWarning`, or `OnInformation`.
- If a rule fails with a severity level equal or higher than the configured level the pipeline will break.
- Engineering:
- Bump xunit to v2.8.1.
[#1840](https://github.com/microsoft/PSRule/pull/1840)

Просмотреть файл

@ -16,6 +16,7 @@ The following workspace options are available for use:
- [Baseline.Group](#baselinegroup)
- [Convention.Include](#conventioninclude)
- [Execution.AliasReference](#executionaliasreference)
- [Execution.Break](#executionbreak)
- [Execution.DuplicateResourceId](#executionduplicateresourceid)
- [Execution.HashAlgorithm](#executionhashalgorithm)
- [Execution.LanguageMode](#executionlanguagemode)
@ -791,6 +792,68 @@ variables:
value: Error
```
### Execution.Break
:octicons-milestone-24: v3.0.0
Determines the minimum rule severity level that breaks the pipeline.
By default, the pipeline will break if a rule of error severity level fails.
For this to take effect the rule must execute successfully and return a failure.
This does not affect the pipeline if other errors or exceptions occurs.
The following preferences are available:
- `None` (0) - No preference.
Inherits the default of `Error`.
- `Never` = (1) - Never break the pipeline if a rule fails regardless of level.
The pipeline will still break if other errors occur.
- `OnError` = (2) - Break the pipeline if a rule of error severity level fails.
This is the default.
- `OnWarning` = (3) - Break the pipeline if a rule of warning or error severity level fails.
- `OnInformation` = (4) - Break the pipeline if a rule of information, warning, or error severity level fails.
This option can be specified using:
```powershell
# PowerShell: Using the Break parameter
$option = New-PSRuleOption -ExecutionBreak 'Never';
```
```powershell
# PowerShell: Using the Execution.Break hashtable key
$option = New-PSRuleOption -Option @{ 'Execution.Break' = 'Never' };
```
```powershell
# PowerShell: Using the ExecutionBreak parameter to set YAML
Set-PSRuleOption -ExecutionBreak 'Never';
```
```yaml
# YAML: Using the execution/break property
execution:
break: Never
```
```bash
# Bash: Using environment variable
export PSRULE_EXECUTION_BREAK=Never
```
```yaml
# GitHub Actions: Using environment variable
env:
PSRULE_EXECUTION_BREAK: Never
```
```yaml
# Azure Pipelines: Using environment variable
variables:
- name: PSRULE_EXECUTION_BREAK
value: Never
```
### Execution.DuplicateResourceId
:octicons-milestone-24: v2.4.0
@ -1517,6 +1580,8 @@ variables:
### Input.FileObjects
:octicons-milestone-24: v3.0.0
Determines if file objects are processed by rules.
This option is for backwards compatibility with PSRule v2.x in cases where file objects are used as input.

Просмотреть файл

@ -356,6 +356,19 @@
"description": "Options that affect execution.",
"markdownDescription": "Options that affect execution. [See help](https://aka.ms/ps-rule/options)",
"properties": {
"break": {
"type": "string",
"title": "Break",
"description": "Determines the minimum rule severity level that breaks the pipeline. By default, the pipeline will break if a rule of error severity fails.",
"markdownDescription": "Determines the minimum rule severity level that breaks the pipeline. By default, the pipeline will break if a rule of error severity fails.\n\n[See help](https://microsoft.github.io/PSRule/v3/concepts/PSRule/en-US/about_PSRule_Options/#executionbreak)",
"enum": [
"Never",
"OnError",
"OnWarning",
"OnInformation"
],
"default": "OnError"
},
"duplicateResourceId": {
"type": "string",
"title": "Duplicate resource identifiers",

Просмотреть файл

@ -53,7 +53,7 @@ public sealed class RunCommand
pipeline.Begin();
pipeline.Process(null);
pipeline.End();
if (pipeline.Result.HadFailures)
if (pipeline.Result.ShouldBreakFromFailure)
exitCode = ERROR_BREAK_ON_FAILURE;
}
return clientContext.Host.HadErrors || pipeline == null ? ERROR_GENERIC : exitCode;

Просмотреть файл

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Options;
/// <summary>
/// Determine the rule severity level at which to break the pipeline.
/// </summary>
public enum BreakLevel
{
/// <summary>
/// No preference.
/// Inherits the default of <c>OnError</c>.
/// </summary>
None = 0,
/// <summary>
/// Continue even if a rule fails regardless of rule severity.
/// </summary>
Never = 1,
/// <summary>
/// Only break on error.
/// </summary>
OnError = 2,
/// <summary>
/// Break if any rule of warning or error severity fails.
/// </summary>
OnWarning = 3,
/// <summary>
/// Break if any rule fails.
/// </summary>
OnInformation = 4
}

Просмотреть файл

@ -5,113 +5,6 @@ using System.ComponentModel;
namespace PSRule.Options;
/// <summary>
/// Options that configure the execution sandbox.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/ps-rule/options"/>.
/// </remarks>
public interface IExecutionOption : IOption
{
/// <summary>
/// Determines how to handle duplicate resources identifiers during execution.
/// Regardless of the value, only the first resource will be used.
/// By defaut, an error is thrown.
/// When set to Warn, a warning is generated.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference DuplicateResourceId { get; }
/// <summary>
/// Configures the hashing algorithm used by the PSRule runtime.
/// The default is <see cref="HashAlgorithm.SHA512"/>.
/// </summary>
HashAlgorithm HashAlgorithm { get; }
/// <summary>
/// The language mode to execute PowerShell code with.
/// The default is <see cref="LanguageMode.FullLanguage"/>.
/// </summary>
LanguageMode LanguageMode { get; }
/// <summary>
/// Determines how the initial session state for executing PowerShell code is created.
/// The default is <see cref="SessionState.BuiltIn"/>.
/// </summary>
SessionState InitialSessionState { get; }
/// <summary>
/// Configures where to allow PowerShell language features (such as rules and conventions) to run from.
/// The default is <see cref="RestrictScriptSource.Unrestricted"/>.
/// </summary>
RestrictScriptSource RestrictScriptSource { get; }
/// <summary>
/// Determines how to handle expired suppression groups.
/// Regardless of the value, an expired suppression group will be ignored.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference SuppressionGroupExpired { get; }
/// <summary>
/// Determines how to handle rules that are excluded.
/// By default, excluded rules do not generated any output.
/// When set to Error, an error is thrown.
/// When set to Warn, a warning is generated.
/// When set to Debug, a message is written to the debug log.
/// </summary>
ExecutionActionPreference RuleExcluded { get; }
/// <summary>
/// Determines how to handle rules that are suppressed.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference RuleSuppressed { get; }
/// <summary>
/// Determines how to handle when an alias to a resource is used.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference AliasReference { get; }
/// <summary>
/// Determines how to handle rules that generate inconclusive results.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference RuleInconclusive { get; }
/// <summary>
/// Determines how to report when an invariant culture is used.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference InvariantCulture { get; }
/// <summary>
/// Determines how to report objects that are not processed by any rule.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference UnprocessedObject { get; }
}
/// <summary>
/// Options that configure the execution sandbox.
/// </summary>
@ -120,6 +13,7 @@ public interface IExecutionOption : IOption
/// </remarks>
public sealed class ExecutionOption : IEquatable<ExecutionOption>, IExecutionOption
{
private const BreakLevel DEFAULT_BREAK = BreakLevel.OnError;
private const LanguageMode DEFAULT_LANGUAGEMODE = Options.LanguageMode.FullLanguage;
private const ExecutionActionPreference DEFAULT_DUPLICATERESOURCEID = ExecutionActionPreference.Error;
private const SessionState DEFAULT_INITIALSESSIONSTATE = SessionState.BuiltIn;
@ -135,6 +29,7 @@ public sealed class ExecutionOption : IEquatable<ExecutionOption>, IExecutionOpt
internal static readonly ExecutionOption Default = new()
{
Break = DEFAULT_BREAK,
DuplicateResourceId = DEFAULT_DUPLICATERESOURCEID,
HashAlgorithm = DEFAULT_HASHALGORITHM,
LanguageMode = DEFAULT_LANGUAGEMODE,
@ -154,6 +49,7 @@ public sealed class ExecutionOption : IEquatable<ExecutionOption>, IExecutionOpt
/// </summary>
public ExecutionOption()
{
Break = null;
DuplicateResourceId = null;
HashAlgorithm = null;
LanguageMode = null;
@ -177,6 +73,7 @@ public sealed class ExecutionOption : IEquatable<ExecutionOption>, IExecutionOpt
if (option == null)
return;
Break = option.Break;
DuplicateResourceId = option.DuplicateResourceId;
HashAlgorithm = option.HashAlgorithm;
LanguageMode = option.LanguageMode;
@ -201,6 +98,7 @@ public sealed class ExecutionOption : IEquatable<ExecutionOption>, IExecutionOpt
public bool Equals(ExecutionOption other)
{
return other != null &&
Break == other.Break &&
DuplicateResourceId == other.DuplicateResourceId &&
HashAlgorithm == other.HashAlgorithm &&
LanguageMode == other.LanguageMode &&
@ -221,6 +119,7 @@ public sealed class ExecutionOption : IEquatable<ExecutionOption>, IExecutionOpt
unchecked // Overflow is fine
{
var hash = 17;
hash = hash * 23 + (Break.HasValue ? Break.Value.GetHashCode() : 0);
hash = hash * 23 + (DuplicateResourceId.HasValue ? DuplicateResourceId.Value.GetHashCode() : 0);
hash = hash * 23 + (HashAlgorithm.HasValue ? HashAlgorithm.Value.GetHashCode() : 0);
hash = hash * 23 + (LanguageMode.HasValue ? LanguageMode.Value.GetHashCode() : 0);
@ -245,6 +144,7 @@ public sealed class ExecutionOption : IEquatable<ExecutionOption>, IExecutionOpt
{
var result = new ExecutionOption(o1)
{
Break = o1?.Break ?? o2?.Break,
DuplicateResourceId = o1?.DuplicateResourceId ?? o2?.DuplicateResourceId,
HashAlgorithm = o1?.HashAlgorithm ?? o2?.HashAlgorithm,
LanguageMode = o1?.LanguageMode ?? o2?.LanguageMode,
@ -261,6 +161,13 @@ public sealed class ExecutionOption : IEquatable<ExecutionOption>, IExecutionOpt
return result;
}
/// <summary>
/// Determines the minimum rule severity level that breaks the pipeline.
/// By default, the pipeline will break if a rule of error severity fails.
/// </summary>
[DefaultValue(null)]
public BreakLevel? Break { get; set; }
/// <summary>
/// Determines how to handle duplicate resources identifiers during execution.
/// Regardless of the value, only the first resource will be used.
@ -371,6 +278,8 @@ public sealed class ExecutionOption : IEquatable<ExecutionOption>, IExecutionOpt
#region IExecutionOption
BreakLevel IExecutionOption.Break => Break ?? DEFAULT_BREAK;
ExecutionActionPreference IExecutionOption.DuplicateResourceId => DuplicateResourceId ?? DEFAULT_DUPLICATERESOURCEID;
HashAlgorithm IExecutionOption.HashAlgorithm => HashAlgorithm ?? DEFAULT_HASHALGORITHM;
@ -402,6 +311,9 @@ public sealed class ExecutionOption : IEquatable<ExecutionOption>, IExecutionOpt
/// </summary>
internal void Load()
{
if (Environment.TryEnum("PSRULE_EXECUTION_BREAK", out BreakLevel @break))
Break = @break;
if (Environment.TryEnum("PSRULE_EXECUTION_HASHALGORITHM", out HashAlgorithm hashAlgorithm))
HashAlgorithm = hashAlgorithm;
@ -444,6 +356,9 @@ public sealed class ExecutionOption : IEquatable<ExecutionOption>, IExecutionOpt
/// </summary>
internal void Load(Dictionary<string, object> index)
{
if (index.TryPopEnum("Execution.Break", out BreakLevel @break))
Break = @break;
if (index.TryPopEnum("Execution.HashAlgorithm", out HashAlgorithm hashAlgorithm))
HashAlgorithm = hashAlgorithm;

Просмотреть файл

@ -0,0 +1,117 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Options;
/// <summary>
/// Options that configure the execution sandbox.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/ps-rule/options"/>.
/// </remarks>
public interface IExecutionOption : IOption
{
/// <summary>
/// Determines the minimum rule severity level that breaks the pipeline.
/// By default, the pipeline will break if a rule of error severity fails.
/// </summary>
BreakLevel Break { get; }
/// <summary>
/// Determines how to handle duplicate resources identifiers during execution.
/// Regardless of the value, only the first resource will be used.
/// By default, an error is thrown.
/// When set to Warn, a warning is generated.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference DuplicateResourceId { get; }
/// <summary>
/// Configures the hashing algorithm used by the PSRule runtime.
/// The default is <see cref="HashAlgorithm.SHA512"/>.
/// </summary>
HashAlgorithm HashAlgorithm { get; }
/// <summary>
/// The language mode to execute PowerShell code with.
/// The default is <see cref="LanguageMode.FullLanguage"/>.
/// </summary>
LanguageMode LanguageMode { get; }
/// <summary>
/// Determines how the initial session state for executing PowerShell code is created.
/// The default is <see cref="SessionState.BuiltIn"/>.
/// </summary>
SessionState InitialSessionState { get; }
/// <summary>
/// Configures where to allow PowerShell language features (such as rules and conventions) to run from.
/// The default is <see cref="RestrictScriptSource.Unrestricted"/>.
/// </summary>
RestrictScriptSource RestrictScriptSource { get; }
/// <summary>
/// Determines how to handle expired suppression groups.
/// Regardless of the value, an expired suppression group will be ignored.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference SuppressionGroupExpired { get; }
/// <summary>
/// Determines how to handle rules that are excluded.
/// By default, excluded rules do not generated any output.
/// When set to Error, an error is thrown.
/// When set to Warn, a warning is generated.
/// When set to Debug, a message is written to the debug log.
/// </summary>
ExecutionActionPreference RuleExcluded { get; }
/// <summary>
/// Determines how to handle rules that are suppressed.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference RuleSuppressed { get; }
/// <summary>
/// Determines how to handle when an alias to a resource is used.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference AliasReference { get; }
/// <summary>
/// Determines how to handle rules that generate inconclusive results.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference RuleInconclusive { get; }
/// <summary>
/// Determines how to report when an invariant culture is used.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference InvariantCulture { get; }
/// <summary>
/// Determines how to report objects that are not processed by any rule.
/// By default, a warning is generated.
/// When set to Error, an error is thrown.
/// When set to Debug, a message is written to the debug log.
/// When set to Ignore, no output will be displayed.
/// </summary>
ExecutionActionPreference UnprocessedObject { get; }
}

Просмотреть файл

@ -1163,6 +1163,10 @@ function New-PSRuleOption {
[Alias('ConventionInclude')]
[String[]]$Convention,
# Sets the Execution.Break option
[Parameter(Mandatory = $False)]
[PSRule.Options.BreakLevel]$ExecutionBreak = [PSRule.Options.BreakLevel]::OnError,
# Sets the Execution.DuplicateResourceId option
[Parameter(Mandatory = $False)]
[Alias('ExecutionDuplicateResourceId')]
@ -1476,6 +1480,10 @@ function Set-PSRuleOption {
[Alias('ConventionInclude')]
[String[]]$Convention,
# Sets the Execution.Break option
[Parameter(Mandatory = $False)]
[PSRule.Options.BreakLevel]$ExecutionBreak = [PSRule.Options.BreakLevel]::OnError,
# Sets the Execution.DuplicateResourceId option
[Parameter(Mandatory = $False)]
[Alias('ExecutionDuplicateResourceId')]
@ -2236,6 +2244,10 @@ function SetOptions {
[Alias('ConventionInclude')]
[String[]]$Convention,
# Sets the Execution.Break option
[Parameter(Mandatory = $False)]
[PSRule.Options.BreakLevel]$ExecutionBreak = [PSRule.Options.BreakLevel]::OnError,
# Sets the Execution.DuplicateResourceId option
[Parameter(Mandatory = $False)]
[Alias('ExecutionDuplicateResourceId')]
@ -2461,6 +2473,11 @@ function SetOptions {
$Option.Convention.Include = $Convention;
}
# Sets option Execution.Break
if ($PSBoundParameters.ContainsKey('ExecutionBreak')) {
$Option.Execution.Break = $ExecutionBreak;
}
# Sets option Execution.DuplicateResourceId
if ($PSBoundParameters.ContainsKey('DuplicateResourceId')) {
$Option.Execution.DuplicateResourceId = $DuplicateResourceId;

Просмотреть файл

@ -44,7 +44,7 @@ internal sealed class AssertPipelineBuilder : InvokePipelineBuilderBase
_ResultVariableName = resultVariableName;
_HostContext = hostContext;
if (!string.IsNullOrEmpty(resultVariableName))
_Results = new List<RuleRecord>();
_Results = [];
_Formatter = GetFormatter(style, source, inner, option);
}
@ -61,7 +61,7 @@ internal sealed class AssertPipelineBuilder : InvokePipelineBuilderBase
return new VisualStudioCodeFormatter(source, inner, option);
return style == OutputStyle.Plain ?
(IAssertFormatter)new PlainFormatter(source, inner, option) :
new PlainFormatter(source, inner, option) :
new ClientFormatter(source, inner, option);
}
@ -100,10 +100,10 @@ internal sealed class AssertPipelineBuilder : InvokePipelineBuilderBase
_Formatter.Begin();
}
public override void End()
public override void End(IPipelineResult result)
{
_Formatter.End(_TotalCount, _FailCount, _ErrorCount);
base.End();
base.End(result);
try
{
if (_ErrorCount > 0)
@ -115,7 +115,7 @@ internal sealed class AssertPipelineBuilder : InvokePipelineBuilderBase
ErrorCategory.InvalidOperation,
null));
}
else if (_FailCount > 0 && _Level == SeverityLevel.Error)
else if (result.ShouldBreakFromFailure)
{
HadFailures = true;
base.WriteError(new ErrorRecord(
@ -133,13 +133,7 @@ internal sealed class AssertPipelineBuilder : InvokePipelineBuilderBase
ErrorCategory.InvalidOperation,
null));
}
if (_FailCount > 0 && _Level == SeverityLevel.Warning)
{
HadFailures = true;
base.WriteWarning(PSRuleResources.RuleFailPipelineException);
}
if (_FailCount > 0 && _Level == SeverityLevel.Information)
else if (_FailCount > 0)
{
HadFailures = true;
base.WriteHost(new HostInformationMessage() { Message = PSRuleResources.RuleFailPipelineException });
@ -150,7 +144,7 @@ internal sealed class AssertPipelineBuilder : InvokePipelineBuilderBase
}
finally
{
_InnerWriter?.End();
_InnerWriter?.End(result);
}
}

Просмотреть файл

@ -0,0 +1,67 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using PSRule.Definitions.Rules;
using PSRule.Options;
namespace PSRule.Pipeline;
internal sealed class DefaultPipelineResult(IPipelineWriter writer, BreakLevel breakLevel) : IPipelineResult
{
private readonly IPipelineWriter _Writer = writer;
private readonly BreakLevel _BreakLevel = breakLevel == BreakLevel.None ? ExecutionOption.Default.Break.Value : breakLevel;
private bool _HadErrors;
private bool _HadFailures;
private SeverityLevel _WorstCase = SeverityLevel.None;
/// <inheritdoc/>
public bool HadErrors
{
get
{
return _HadErrors || (_Writer != null && _Writer.HadErrors);
}
set
{
_HadErrors = value;
}
}
/// <inheritdoc/>
public bool HadFailures
{
get
{
return _HadFailures || (_Writer != null && _Writer.HadFailures);
}
set
{
_HadFailures = value;
}
}
/// <inheritdoc/>
public bool ShouldBreakFromFailure { get; private set; }
public void Fail(SeverityLevel level)
{
_WorstCase = _WorstCase.GetWorstCase(level);
_HadFailures = true;
if (ShouldBreakFromFailure || _BreakLevel == BreakLevel.Never || _WorstCase == SeverityLevel.None)
return;
if (_BreakLevel == BreakLevel.OnInformation && (_WorstCase == SeverityLevel.Information || _WorstCase == SeverityLevel.Warning || _WorstCase == SeverityLevel.Error))
{
ShouldBreakFromFailure = true;
}
else if (_BreakLevel == BreakLevel.OnWarning && (_WorstCase == SeverityLevel.Warning || _WorstCase == SeverityLevel.Error))
{
ShouldBreakFromFailure = true;
}
else if (_BreakLevel == BreakLevel.OnError && _WorstCase == SeverityLevel.Error)
{
ShouldBreakFromFailure = true;
}
}
}

Просмотреть файл

@ -176,7 +176,7 @@ internal abstract class AssertFormatterBase : PipelineLoggerBase, IAssertFormatt
public void End(int total, int fail, int error)
{
if (Option.Output.Footer.GetValueOrDefault(FooterFormat.Default) != FooterFormat.None)
LineBreak();
BreakIfUnbrokenInfo();
FooterRuleCount(total, fail, error);
FooterRunInfo();

Просмотреть файл

@ -26,7 +26,7 @@ internal sealed class GetBaselinePipeline : RulePipeline
public override void End()
{
Writer.WriteObject(HostHelper.GetBaseline(Source, Context).Where(Match), true);
Writer.End();
Writer.End(Result);
}
private bool Match(Baseline baseline)

Просмотреть файл

@ -24,6 +24,6 @@ internal sealed class GetRulePipeline : RulePipeline, IPipeline
public override void End()
{
Writer.WriteObject(HostHelper.GetRule(Source, Context, _IncludeDependencies), true);
Writer.End();
Writer.End(Result);
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
namespace PSRule.Pipeline;
/// <summary>
/// An instance of a PSRule pipeline.
/// </summary>
public interface IPipeline : IDisposable
{
/// <summary>
/// Get the pipeline result.
/// </summary>
IPipelineResult Result { get; }
/// <summary>
/// Initialize the pipeline and results. Call this method once prior to calling Process.
/// </summary>
void Begin();
/// <summary>
/// Process an object through the pipeline. Each object will be processed by rules that apply based on pre-conditions.
/// </summary>
/// <param name="sourceObject">The object to process.</param>
void Process(PSObject sourceObject);
/// <summary>
/// Clean up and flush pipeline results. Call this method once after processing any objects through the pipeline.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Matches PowerShell pipeline.")]
void End();
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Pipeline;
/// <summary>
/// A result from the pipeline.
/// </summary>
public interface IPipelineResult
{
/// <summary>
/// Determines if any errors were reported.
/// </summary>
public bool HadErrors { get; }
/// <summary>
/// Determines if an failures were reported.
/// </summary>
public bool HadFailures { get; }
/// <summary>
/// Determines if the pipeline should break from rules that failed.
/// </summary>
public bool ShouldBreakFromFailure { get; }
}

Просмотреть файл

@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
namespace PSRule.Pipeline;
/// <summary>
/// An writer which receives output from PSRule.
/// </summary>
public interface IPipelineWriter : IDisposable
{
/// <summary>
/// Determines if any errors were reported.
/// </summary>
bool HadErrors { get; }
/// <summary>
/// Determines if any failures were reported.
/// </summary>
bool HadFailures { get; }
/// <summary>
/// Write a verbose message.
/// </summary>
void WriteVerbose(string message);
/// <summary>
/// Determines if a verbose message should be written to output.
/// </summary>
bool ShouldWriteVerbose();
/// <summary>
/// Write a warning message.
/// </summary>
void WriteWarning(string message);
/// <summary>
/// Determines if a warning message should be written to output.
/// </summary>
bool ShouldWriteWarning();
/// <summary>
/// Write an error message.
/// </summary>
void WriteError(ErrorRecord errorRecord);
/// <summary>
/// Determines if an error message should be written to output.
/// </summary>
bool ShouldWriteError();
/// <summary>
/// Write an informational message.
/// </summary>
void WriteInformation(InformationRecord informationRecord);
/// <summary>
/// Write a message to the host process.
/// </summary>
void WriteHost(HostInformationMessage info);
/// <summary>
/// Determines if an informational message should be written to output.
/// </summary>
bool ShouldWriteInformation();
/// <summary>
/// Write a debug message.
/// </summary>
void WriteDebug(string text, params object[] args);
/// <summary>
/// Determines if a debug message should be written to output.
/// </summary>
bool ShouldWriteDebug();
/// <summary>
/// Write an object to output.
/// </summary>
/// <param name="sendToPipeline">The object to write to the pipeline.</param>
/// <param name="enumerateCollection">Determines when the object is enumerable if it should be enumerated as more then one object.</param>
void WriteObject(object sendToPipeline, bool enumerateCollection);
/// <summary>
/// Enter a logging scope.
/// </summary>
void EnterScope(string scopeName);
/// <summary>
/// Exit a logging scope.
/// </summary>
void ExitScope();
/// <summary>
/// Start and initialize the writer.
/// </summary>
void Begin();
/// <summary>
/// Stop and finalized the writer.
/// </summary>
void End(IPipelineResult result);
}

Просмотреть файл

@ -35,13 +35,13 @@ internal sealed class InvokeRulePipeline : RulePipeline, IPipeline
_Outcome = outcome;
_IsSummary = context.Option.Output.As.Value == ResultFormat.Summary;
_Summary = _IsSummary ? new Dictionary<string, RuleSummaryRecord>() : null;
_Summary = _IsSummary ? [] : null;
var allRuleBlocks = _RuleGraph.GetAll();
var resourceIndex = new ResourceIndex(allRuleBlocks);
_SuppressionFilter = new SuppressionFilter(Context, context.Option.Suppression, resourceIndex);
_SuppressionGroupFilter = new SuppressionFilter(Pipeline.SuppressionGroup, resourceIndex);
_Completed = new List<InvokeResult>();
_Completed = [];
}
public int RuleCount { get; private set; }
@ -84,7 +84,7 @@ internal sealed class InvokeRulePipeline : RulePipeline, IPipeline
if (_IsSummary)
Writer.WriteObject(_Summary.Values.Where(r => _Outcome == RuleOutcome.All || (r.Outcome & _Outcome) > 0).ToArray(), true);
Writer.End();
Writer.End(Result);
}
private InvokeResult ProcessTargetObject(TargetObject targetObject)
@ -148,7 +148,7 @@ internal sealed class InvokeRulePipeline : RulePipeline, IPipeline
}
else if (ruleRecord.Outcome == RuleOutcome.Fail)
{
Result.HadFailures = true;
Result.Fail(ruleRecord.Level);
ruleBlockTarget.Fail();
Context.Fail();
}

Просмотреть файл

@ -47,10 +47,10 @@ internal sealed class JobSummaryWriter : ResultOutputWriter<InvokeResult>
base.Begin();
}
public sealed override void End()
public sealed override void End(IPipelineResult result)
{
Flush();
base.End();
base.End(result);
}
#region Helper methods

Просмотреть файл

@ -3,7 +3,6 @@
using System.Collections;
using System.Globalization;
using System.Management.Automation;
using PSRule.Configuration;
using PSRule.Data;
using PSRule.Definitions;
@ -189,88 +188,6 @@ public interface IPipelineBuilder
IPipeline Build(IPipelineWriter writer = null);
}
/// <summary>
/// An instance of a PSRule pipeline.
/// </summary>
public interface IPipeline : IDisposable
{
/// <summary>
/// Get the pipeline result.
/// </summary>
IPipelineResult Result { get; }
/// <summary>
/// Initialize the pipeline and results. Call this method once prior to calling Process.
/// </summary>
void Begin();
/// <summary>
/// Process an object through the pipeline. Each object will be processed by rules that apply based on pre-conditions.
/// </summary>
/// <param name="sourceObject">The object to process.</param>
void Process(PSObject sourceObject);
/// <summary>
/// Clean up and flush pipeline results. Call this method once after processing any objects through the pipeline.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Matches PowerShell pipeline.")]
void End();
}
/// <summary>
/// A result from the pipeline.
/// </summary>
public interface IPipelineResult
{
/// <summary>
/// Determines if any errors were reported.
/// </summary>
public bool HadErrors { get; }
/// <summary>
/// Determines if an failures were reported.
/// </summary>
public bool HadFailures { get; }
}
internal sealed class DefaultPipelineResult : IPipelineResult
{
private readonly IPipelineWriter _Writer;
private bool _HadErrors;
private bool _HadFailures;
public DefaultPipelineResult(IPipelineWriter writer)
{
_Writer = writer;
}
/// <inheritdoc/>
public bool HadErrors
{
get
{
return _HadErrors || (_Writer != null && _Writer.HadErrors);
}
set
{
_HadErrors = value;
}
}
/// <inheritdoc/>
public bool HadFailures
{
get
{
return _HadFailures || (_Writer != null && _Writer.HadFailures);
}
set
{
_HadFailures = value;
}
}
}
internal abstract class PipelineBuilderBase : IPipelineBuilder
{
private const string ENGINE_MODULE_NAME = "PSRule";
@ -413,7 +330,7 @@ internal abstract class PipelineBuilderBase : IPipelineBuilder
{
var writer = PrepareWriter();
writer.ErrorRequiredVersionMismatch(moduleName, moduleVersion, requiredVersion);
writer.End();
writer.End(new DefaultPipelineResult(null, BreakLevel.None) { HadErrors = true });
return true;
}
return false;
@ -505,7 +422,7 @@ internal abstract class PipelineBuilderBase : IPipelineBuilder
shouldProcess: HostContext.ShouldProcess,
writeHost: writeHost
)
: (PipelineWriter)_Output;
: _Output;
}
protected static string[] GetCulture(string[] culture)
@ -580,8 +497,7 @@ internal abstract class PipelineBuilderBase : IPipelineBuilder
var ignoreGitPath = Option.Input.IgnoreGitPath ?? InputOption.Default.IgnoreGitPath.Value;
var ignoreRepositoryCommon = Option.Input.IgnoreRepositoryCommon ?? InputOption.Default.IgnoreRepositoryCommon.Value;
var builder = PathFilterBuilder.Create(basePath, Option.Input.PathIgnore, ignoreGitPath, ignoreRepositoryCommon);
//if (Option.Input.Format == InputFormat.File)
builder.UseGitIgnore();
builder.UseGitIgnore();
_InputFilter = builder.Build();
}

Просмотреть файл

@ -123,7 +123,7 @@ internal abstract class PipelineLoggerBase : IPipelineWriter
}
public virtual void End()
public virtual void End(IPipelineResult result)
{
}

Просмотреть файл

@ -8,108 +8,10 @@ using PSRule.Rules;
namespace PSRule.Pipeline;
/// <summary>
/// An writer which recieves output from PSRule.
/// </summary>
public interface IPipelineWriter : IDisposable
{
/// <summary>
/// Determines if any errors were reported.
/// </summary>
bool HadErrors { get; }
/// <summary>
/// Determines if an failures were reported.
/// </summary>
bool HadFailures { get; }
/// <summary>
/// Write a verbose message.
/// </summary>
void WriteVerbose(string message);
/// <summary>
/// Determines if a verbose message should be written to output.
/// </summary>
bool ShouldWriteVerbose();
/// <summary>
/// Write a warning message.
/// </summary>
void WriteWarning(string message);
/// <summary>
/// Determines if a warning message should be written to output.
/// </summary>
bool ShouldWriteWarning();
/// <summary>
/// Write an error message.
/// </summary>
void WriteError(ErrorRecord errorRecord);
/// <summary>
/// Determines if an error message should be written to output.
/// </summary>
bool ShouldWriteError();
/// <summary>
/// Write an informational message.
/// </summary>
void WriteInformation(InformationRecord informationRecord);
/// <summary>
/// Write a message to the host process.
/// </summary>
void WriteHost(HostInformationMessage info);
/// <summary>
/// Determines if an informational message should be written to output.
/// </summary>
bool ShouldWriteInformation();
/// <summary>
/// Write a debug message.
/// </summary>
void WriteDebug(string text, params object[] args);
/// <summary>
/// Determines if a debug message should be written to output.
/// </summary>
bool ShouldWriteDebug();
/// <summary>
/// Write an object to output.
/// </summary>
/// <param name="sendToPipeline">The object to write to the pipeline.</param>
/// <param name="enumerateCollection">Determines when the object is enumerable if it should be enumerated as more then one object.</param>
void WriteObject(object sendToPipeline, bool enumerateCollection);
/// <summary>
/// Enter a logging scope.
/// </summary>
void EnterScope(string scopeName);
/// <summary>
/// Exit a logging scope.
/// </summary>
void ExitScope();
/// <summary>
/// Start and initialize the writer.
/// </summary>
void Begin();
/// <summary>
/// Stop and finalized the writer.
/// </summary>
void End();
}
/// <summary>
/// A base class for writers.
/// </summary>
internal abstract class PipelineWriter : IPipelineWriter
internal abstract class PipelineWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) : IPipelineWriter
{
protected const string ErrorPreference = "ErrorActionPreference";
protected const string WarningPreference = "WarningPreference";
@ -117,22 +19,15 @@ internal abstract class PipelineWriter : IPipelineWriter
protected const string InformationPreference = "InformationPreference";
protected const string DebugPreference = "DebugPreference";
private readonly IPipelineWriter _Writer;
private readonly ShouldProcess _ShouldProcess;
private readonly IPipelineWriter _Writer = inner;
private readonly ShouldProcess _ShouldProcess = shouldProcess;
protected readonly PSRuleOption Option;
protected readonly PSRuleOption Option = option;
private bool _IsDisposed;
private bool _HadErrors;
private bool _HadFailures;
protected PipelineWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess)
{
_Writer = inner;
_ShouldProcess = shouldProcess;
Option = option;
}
bool IPipelineWriter.HadErrors => HadErrors;
bool IPipelineWriter.HadFailures => HadFailures;
@ -182,12 +77,12 @@ internal abstract class PipelineWriter : IPipelineWriter
}
/// <inheritdoc/>
public virtual void End()
public virtual void End(IPipelineResult result)
{
if (_Writer == null)
return;
_Writer.End();
_Writer.End(result);
}
/// <inheritdoc/>
@ -363,98 +258,8 @@ internal abstract class PipelineWriter : IPipelineWriter
/// <summary>
/// Get the value of a preference variable.
/// </summary>
protected static ActionPreference GetPreferenceVariable(System.Management.Automation.SessionState sessionState, string variableName)
protected static ActionPreference GetPreferenceVariable(SessionState sessionState, string variableName)
{
return (ActionPreference)sessionState.PSVariable.GetValue(variableName);
}
}
internal abstract class ResultOutputWriter<T> : PipelineWriter
{
private readonly List<T> _Result;
protected ResultOutputWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess)
: base(inner, option, shouldProcess)
{
_Result = new List<T>();
}
public override void WriteObject(object sendToPipeline, bool enumerateCollection)
{
if (sendToPipeline is InvokeResult && Option.Output.As == ResultFormat.Summary)
{
base.WriteObject(sendToPipeline, enumerateCollection);
return;
}
if (sendToPipeline is InvokeResult result)
{
Add(typeof(T) == typeof(RuleRecord) ? result.AsRecord() : result);
}
else
{
Add(sendToPipeline);
}
base.WriteObject(sendToPipeline, enumerateCollection);
}
protected void Add(object o)
{
if (o is T[] collection)
_Result.AddRange(collection);
else if (o is T item)
_Result.Add(item);
}
/// <summary>
/// Clear any buffers from the writer.
/// </summary>
protected virtual void Flush() { }
protected T[] GetResults()
{
return _Result.ToArray();
}
}
internal abstract class SerializationOutputWriter<T> : ResultOutputWriter<T>
{
protected SerializationOutputWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess)
: base(inner, option, shouldProcess) { }
public sealed override void End()
{
var results = GetResults();
base.WriteObject(Serialize(results), false);
ProcessError(results);
Flush();
base.End();
}
public override void WriteObject(object sendToPipeline, bool enumerateCollection)
{
if (sendToPipeline is InvokeResult && Option.Output.As == ResultFormat.Summary)
{
base.WriteObject(sendToPipeline, enumerateCollection);
return;
}
if (sendToPipeline is InvokeResult result)
{
Add(result.AsRecord());
return;
}
Add(sendToPipeline);
}
protected abstract string Serialize(T[] o);
private void ProcessError(T[] results)
{
for (var i = 0; i < results.Length; i++)
{
if (results[i] is RuleRecord record)
WriteErrorInfo(record);
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using PSRule.Configuration;
using PSRule.Rules;
namespace PSRule.Pipeline;
internal abstract class ResultOutputWriter<T> : PipelineWriter
{
private readonly List<T> _Result;
protected ResultOutputWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess)
: base(inner, option, shouldProcess)
{
_Result = new List<T>();
}
public override void WriteObject(object sendToPipeline, bool enumerateCollection)
{
if (sendToPipeline is InvokeResult && Option.Output.As == ResultFormat.Summary)
{
base.WriteObject(sendToPipeline, enumerateCollection);
return;
}
if (sendToPipeline is InvokeResult result)
{
Add(typeof(T) == typeof(RuleRecord) ? result.AsRecord() : result);
}
else
{
Add(sendToPipeline);
}
base.WriteObject(sendToPipeline, enumerateCollection);
}
protected void Add(object o)
{
if (o is T[] collection)
_Result.AddRange(collection);
else if (o is T item)
_Result.Add(item);
}
/// <summary>
/// Clear any buffers from the writer.
/// </summary>
protected virtual void Flush() { }
protected T[] GetResults()
{
return _Result.ToArray();
}
}

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System.Management.Automation;
using PSRule.Options;
using PSRule.Runtime;
namespace PSRule.Pipeline;
@ -19,7 +20,7 @@ internal abstract class RulePipeline : IPipeline
protected RulePipeline(PipelineContext context, Source[] source, PipelineInputStream reader, IPipelineWriter writer)
{
Result = new DefaultPipelineResult(writer);
Result = new DefaultPipelineResult(writer, context.Option.Execution.Break.GetValueOrDefault(ExecutionOption.Default.Break.Value));
Pipeline = context;
Context = new RunspaceContext(Pipeline, writer);
Source = source;
@ -55,7 +56,7 @@ internal abstract class RulePipeline : IPipeline
/// <inheritdoc/>
public virtual void End()
{
Writer.End();
Writer.End(Result);
}
#endregion IPipeline

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using PSRule.Configuration;
using PSRule.Rules;
namespace PSRule.Pipeline;
internal abstract class SerializationOutputWriter<T> : ResultOutputWriter<T>
{
protected SerializationOutputWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess)
: base(inner, option, shouldProcess) { }
public sealed override void End(IPipelineResult result)
{
var results = GetResults();
base.WriteObject(Serialize(results), false);
ProcessError(results);
Flush();
base.End(result);
}
public override void WriteObject(object sendToPipeline, bool enumerateCollection)
{
if (sendToPipeline is InvokeResult && Option.Output.As == ResultFormat.Summary)
{
base.WriteObject(sendToPipeline, enumerateCollection);
return;
}
if (sendToPipeline is InvokeResult result)
{
Add(result.AsRecord());
return;
}
Add(sendToPipeline);
}
protected abstract string Serialize(T[] o);
private void ProcessError(T[] results)
{
for (var i = 0; i < results.Length; i++)
{
if (results[i] is RuleRecord record)
WriteErrorInfo(record);
}
}
}

Просмотреть файл

@ -21,12 +21,13 @@ public sealed class AssertFormatterTests
{
var option = GetOption();
option.Output.Banner = BannerFormat.None;
option.Output.Footer = FooterFormat.None;
var writer = GetWriter();
// Check output is empty
var formatter = new PlainFormatter(null, writer, option);
formatter.Begin();
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal("", writer.Output);
// Check pass output
@ -34,7 +35,7 @@ public sealed class AssertFormatterTests
formatter = new PlainFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetPassResult());
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [1/1]
[PASS] Test
@ -45,7 +46,7 @@ public sealed class AssertFormatterTests
formatter = new PlainFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult());
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -59,7 +60,7 @@ public sealed class AssertFormatterTests
formatter = new PlainFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult(SeverityLevel.Warning));
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -73,7 +74,7 @@ public sealed class AssertFormatterTests
formatter = new PlainFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult(SeverityLevel.Information));
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -88,12 +89,13 @@ public sealed class AssertFormatterTests
{
var option = GetOption();
option.Output.Banner = BannerFormat.None;
option.Output.Footer = FooterFormat.None;
var writer = GetWriter();
// Check output is empty
var formatter = new ClientFormatter(null, writer, option);
formatter.Begin();
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal("", writer.Output);
// Check pass output
@ -101,7 +103,7 @@ public sealed class AssertFormatterTests
formatter = new ClientFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetPassResult());
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [1/1]
[PASS] Test
@ -112,7 +114,7 @@ public sealed class AssertFormatterTests
formatter = new ClientFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult());
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -126,7 +128,7 @@ public sealed class AssertFormatterTests
formatter = new ClientFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult(SeverityLevel.Warning));
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -140,7 +142,7 @@ public sealed class AssertFormatterTests
formatter = new ClientFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult(SeverityLevel.Information));
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -155,12 +157,13 @@ public sealed class AssertFormatterTests
{
var option = GetOption();
option.Output.Banner = BannerFormat.None;
option.Output.Footer = FooterFormat.None;
var writer = GetWriter();
// Check output is empty
var formatter = new AzurePipelinesFormatter(null, writer, option);
formatter.Begin();
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal("", writer.Output);
// Check pass output
@ -168,7 +171,7 @@ public sealed class AssertFormatterTests
formatter = new AzurePipelinesFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetPassResult());
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [1/1]
[PASS] Test
@ -179,7 +182,7 @@ public sealed class AssertFormatterTests
formatter = new AzurePipelinesFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult());
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -197,7 +200,7 @@ public sealed class AssertFormatterTests
formatter = new AzurePipelinesFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult(SeverityLevel.Warning));
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -215,7 +218,7 @@ public sealed class AssertFormatterTests
formatter = new AzurePipelinesFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult(SeverityLevel.Information));
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -230,12 +233,13 @@ public sealed class AssertFormatterTests
{
var option = GetOption();
option.Output.Banner = BannerFormat.None;
option.Output.Footer = FooterFormat.None;
var writer = GetWriter();
// Check output is empty
var formatter = new GitHubActionsFormatter(null, writer, option);
formatter.Begin();
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal("", writer.Output);
// Check pass output
@ -243,7 +247,7 @@ public sealed class AssertFormatterTests
formatter = new GitHubActionsFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetPassResult());
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [1/1]
[PASS] Test
@ -254,7 +258,7 @@ public sealed class AssertFormatterTests
formatter = new GitHubActionsFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult());
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -272,7 +276,7 @@ public sealed class AssertFormatterTests
formatter = new GitHubActionsFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult(SeverityLevel.Warning));
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -290,7 +294,7 @@ public sealed class AssertFormatterTests
formatter = new GitHubActionsFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult(SeverityLevel.Information));
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@" -> TestObject : TestType [0/2]
[FAIL] Test1
@ -309,12 +313,13 @@ public sealed class AssertFormatterTests
{
var option = GetOption();
option.Output.Banner = BannerFormat.None;
option.Output.Footer = FooterFormat.None;
var writer = GetWriter();
// Check output is empty
var formatter = new VisualStudioCodeFormatter(null, writer, option);
formatter.Begin();
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal("", writer.Output);
// Check pass output
@ -322,7 +327,7 @@ public sealed class AssertFormatterTests
formatter = new VisualStudioCodeFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetPassResult());
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@"> TestObject : TestType [1/1]
PASS Test
@ -333,7 +338,7 @@ public sealed class AssertFormatterTests
formatter = new VisualStudioCodeFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult());
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@"> TestObject : TestType [0/2]
FAIL Test1
@ -347,7 +352,7 @@ public sealed class AssertFormatterTests
formatter = new VisualStudioCodeFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult(SeverityLevel.Warning));
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@"> TestObject : TestType [0/2]
FAIL Test1
@ -361,7 +366,7 @@ public sealed class AssertFormatterTests
formatter = new VisualStudioCodeFormatter(null, writer, option);
formatter.Begin();
formatter.Result(GetFailResult(SeverityLevel.Information));
formatter.End();
formatter.End(0, 0, 0);
Assert.Equal(@"> TestObject : TestType [0/2]
FAIL Test1

Просмотреть файл

@ -34,7 +34,7 @@ public sealed class OutputWriterTests
var writer = new SarifOutputWriter(null, output, option, null);
writer.Begin();
writer.WriteObject(result, false);
writer.End();
writer.End(new DefaultPipelineResult(null, Options.BreakLevel.None));
var actual = JsonConvert.DeserializeObject<JObject>(output.Output.OfType<string>().FirstOrDefault());
Assert.NotNull(actual);
@ -78,7 +78,7 @@ public sealed class OutputWriterTests
var writer = new SarifOutputWriter(null, output, option, null);
writer.Begin();
writer.WriteObject(result, false);
writer.End();
writer.End(new DefaultPipelineResult(null, Options.BreakLevel.None));
var actual = JsonConvert.DeserializeObject<JObject>(output.Output.OfType<string>().FirstOrDefault());
Assert.NotNull(actual);
@ -112,7 +112,7 @@ public sealed class OutputWriterTests
var writer = new YamlOutputWriter(output, option, null);
writer.Begin();
writer.WriteObject(result, false);
writer.End();
writer.End(new DefaultPipelineResult(null, Options.BreakLevel.None));
Assert.Equal(@"- detail:
reason: []
@ -210,7 +210,7 @@ public sealed class OutputWriterTests
var writer = new JsonOutputWriter(output, option, null);
writer.Begin();
writer.WriteObject(result, false);
writer.End();
writer.End(new DefaultPipelineResult(null, Options.BreakLevel.None));
Assert.Equal(@"[
{
@ -331,7 +331,7 @@ public sealed class OutputWriterTests
var writer = new NUnit3OutputWriter(output, option, null);
writer.Begin();
writer.WriteObject(result, false);
writer.End();
writer.End(new DefaultPipelineResult(null, Options.BreakLevel.None));
var s = output.Output.OfType<string>().FirstOrDefault();
var doc = new XmlDocument();
@ -357,7 +357,7 @@ public sealed class OutputWriterTests
var writer = new CsvOutputWriter(output, option, null);
writer.Begin();
writer.WriteObject(result, false);
writer.End();
writer.End(new DefaultPipelineResult(null, Options.BreakLevel.None));
var actual = output.Output.OfType<string>().FirstOrDefault();
@ -384,7 +384,7 @@ public sealed class OutputWriterTests
writer.Begin();
writer.WriteObject(result, false);
context.RunTime.Stop();
writer.End();
writer.End(new DefaultPipelineResult(null, Options.BreakLevel.None));
stream.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(stream);

Просмотреть файл

@ -635,6 +635,145 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' {
}
}
Context 'Read Execution.AliasReference' {
It 'from default' {
$option = New-PSRuleOption -Default;
$option.Execution.AliasReference | Should -Be 'Warn';
}
It 'from Hashtable' {
$option = New-PSRuleOption -Option @{ 'Execution.AliasReference' = 'error' };
$option.Execution.AliasReference | Should -Be 'Error';
$option = New-PSRuleOption -Option @{ 'Execution.AliasReference' = 'Error' };
$option.Execution.AliasReference | Should -Be 'Error';
}
It 'from YAML' {
$option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml');
$option.Execution.AliasReference | Should -Be 'Ignore';
}
It 'from Environment' {
try {
# With enum
$Env:PSRULE_EXECUTION_ALIASREFERENCE = 'error';
$option = New-PSRuleOption;
$option.Execution.AliasReference | Should -Be 'Error';
# With enum
$Env:PSRULE_EXECUTION_ALIASREFERENCE = 'Error';
$option = New-PSRuleOption;
$option.Execution.AliasReference | Should -Be 'Error';
# With int
$Env:PSRULE_EXECUTION_ALIASREFERENCE = '3';
$option = New-PSRuleOption;
$option.Execution.AliasReference | Should -Be 'Error';
}
finally {
Remove-Item 'Env:PSRULE_EXECUTION_ALIASREFERENCE' -Force;
}
}
It 'from parameter' {
$option = New-PSRuleOption -ExecutionAliasReference 'Error' -Path $emptyOptionsFilePath;
$option.Execution.AliasReference | Should -Be 'Error';
}
}
Context 'Read Execution.Break' {
It 'from default' {
$option = New-PSRuleOption -Default;
$option.Execution.Break | Should -Be 'OnError';
}
It 'from Hashtable' {
$option = New-PSRuleOption -Option @{ 'Execution.Break' = 'never' };
$option.Execution.Break | Should -Be 'Never';
$option = New-PSRuleOption -Option @{ 'Execution.Break' = 'Never' };
$option.Execution.Break | Should -Be 'Never';
}
It 'from YAML' {
$option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests16.yml');
$option.Execution.Break | Should -Be 'Never';
}
It 'from Environment' {
try {
# With enum
$Env:PSRULE_EXECUTION_BREAK = 'never';
$option = New-PSRuleOption;
$option.Execution.Break | Should -Be 'Never';
$Env:PSRULE_EXECUTION_BREAK = 'never';
$option = New-PSRuleOption;
$option.Execution.Break | Should -Be 'Never';
# With int
$Env:PSRULE_EXECUTION_BREAK = '1';
$option = New-PSRuleOption;
$option.Execution.Break | Should -Be 'Never';
}
finally {
Remove-Item 'Env:PSRULE_EXECUTION_BREAK' -Force;
}
}
It 'from parameter' {
$option = New-PSRuleOption -ExecutionBreak 'Never' -Path $emptyOptionsFilePath;
$option.Execution.Break | Should -Be 'Never';
}
}
Context 'Read Execution.DuplicateResourceId' {
It 'from default' {
$option = New-PSRuleOption -Default;
$option.Execution.DuplicateResourceId | Should -Be 'Error'
}
It 'from Hashtable' {
$option = New-PSRuleOption -Option @{ 'Execution.DuplicateResourceId' = 'warn' };
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
$option = New-PSRuleOption -Option @{ 'Execution.DuplicateResourceId' = 'Warn' };
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
}
It 'from YAML' {
$option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml');
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
}
It 'from Environment' {
try {
# With enum
$Env:PSRULE_EXECUTION_DUPLICATERESOURCEID = 'warn';
$option = New-PSRuleOption;
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
$Env:PSRULE_EXECUTION_DUPLICATERESOURCEID = 'Warn';
$option = New-PSRuleOption;
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
# With int
$Env:PSRULE_EXECUTION_DUPLICATERESOURCEID = '2';
$option = New-PSRuleOption;
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
}
finally {
Remove-Item 'Env:PSRULE_EXECUTION_DUPLICATERESOURCEID' -Force;
}
}
It 'from parameter' {
$option = New-PSRuleOption -DuplicateResourceId 'Warn' -Path $emptyOptionsFilePath;
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
}
}
Context 'Read Execution.HashAlgorithm' {
It 'from default' {
$option = New-PSRuleOption -Default;
@ -827,53 +966,6 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' {
}
}
Context 'Read Execution.AliasReference' {
It 'from default' {
$option = New-PSRuleOption -Default;
$option.Execution.AliasReference | Should -Be 'Warn';
}
It 'from Hashtable' {
$option = New-PSRuleOption -Option @{ 'Execution.AliasReference' = 'error' };
$option.Execution.AliasReference | Should -Be 'Error';
$option = New-PSRuleOption -Option @{ 'Execution.AliasReference' = 'Error' };
$option.Execution.AliasReference | Should -Be 'Error';
}
It 'from YAML' {
$option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml');
$option.Execution.AliasReference | Should -Be 'Ignore';
}
It 'from Environment' {
try {
# With enum
$Env:PSRULE_EXECUTION_ALIASREFERENCE = 'error';
$option = New-PSRuleOption;
$option.Execution.AliasReference | Should -Be 'Error';
# With enum
$Env:PSRULE_EXECUTION_ALIASREFERENCE = 'Error';
$option = New-PSRuleOption;
$option.Execution.AliasReference | Should -Be 'Error';
# With int
$Env:PSRULE_EXECUTION_ALIASREFERENCE = '3';
$option = New-PSRuleOption;
$option.Execution.AliasReference | Should -Be 'Error';
}
finally {
Remove-Item 'Env:PSRULE_EXECUTION_ALIASREFERENCE' -Force;
}
}
It 'from parameter' {
$option = New-PSRuleOption -ExecutionAliasReference 'Error' -Path $emptyOptionsFilePath;
$option.Execution.AliasReference | Should -Be 'Error';
}
}
Context 'Read Execution.RuleInconclusive' {
It 'from default' {
$option = New-PSRuleOption -Default;
@ -1015,53 +1107,6 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' {
}
}
Context 'Read Execution.DuplicateResourceId' {
It 'from default' {
$option = New-PSRuleOption -Default;
$option.Execution.DuplicateResourceId | Should -Be 'Error'
}
It 'from Hashtable' {
$option = New-PSRuleOption -Option @{ 'Execution.DuplicateResourceId' = 'warn' };
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
$option = New-PSRuleOption -Option @{ 'Execution.DuplicateResourceId' = 'Warn' };
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
}
It 'from YAML' {
$option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml');
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
}
It 'from Environment' {
try {
# With enum
$Env:PSRULE_EXECUTION_DUPLICATERESOURCEID = 'warn';
$option = New-PSRuleOption;
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
# With enum
$Env:PSRULE_EXECUTION_DUPLICATERESOURCEID = 'Warn';
$option = New-PSRuleOption;
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
# With int
$Env:PSRULE_EXECUTION_DUPLICATERESOURCEID = '2';
$option = New-PSRuleOption;
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
}
finally {
Remove-Item 'Env:PSRULE_EXECUTION_DUPLICATERESOURCEID' -Force;
}
}
It 'from parameter' {
$option = New-PSRuleOption -DuplicateResourceId 'Warn' -Path $emptyOptionsFilePath;
$option.Execution.DuplicateResourceId | Should -Be 'Warn';
}
}
Context 'Read Execution.InitialSessionState' {
It 'from default' {
$option = New-PSRuleOption -Default;
@ -2341,6 +2386,13 @@ Describe 'Set-PSRuleOption' -Tag 'Option','Set-PSRuleOption' {
}
}
Context 'Read Execution.Break' {
It 'from parameter' {
$option = Set-PSRuleOption -ExecutionBreak 'Never' @optionParams;
$option.Execution.Break | Should -Be 'Never';
}
}
Context 'Read Execution.SuppressionGroupExpired' {
It 'from parameter' {
$option = Set-PSRuleOption -SuppressionGroupExpired 'Error' @optionParams;

Просмотреть файл

@ -0,0 +1,5 @@
# These are options for unit tests
# Configure execution options.
execution:
break: Never

Просмотреть файл

@ -106,7 +106,6 @@ public sealed class PipelineTests
var option = GetOption();
option.Rule.Include = ["WithPathPrefix"];
//option.Input.Format = InputFormat.File;
var builder = PipelineBuilder.Invoke(GetSource(), option, null);
var writer = new TestWriter(option);
var pipeline = builder.Build(writer);