From a7b1edc23ebb7546c719435bd133d7b629ed64c0 Mon Sep 17 00:00:00 2001
From: Leon van der Meer <64834803+leonvandermeer@users.noreply.github.com>
Date: Fri, 25 Oct 2024 23:35:39 +0200
Subject: [PATCH 1/4] Implement WakeLock so that the system does not enter
sleep while benchmarks are running
---
docs/articles/guides/console-args.md | 1 +
docs/articles/samples/IntroWakeLock.md | 32 +++
docs/articles/samples/toc.yml | 2 +
.../BenchmarkDotNet.Samples/IntroWakeLock.cs | 30 +++
.../Attributes/WakeLockAttribute.cs | 18 ++
src/BenchmarkDotNet/Configs/DebugConfig.cs | 1 +
src/BenchmarkDotNet/Configs/DefaultConfig.cs | 2 +
src/BenchmarkDotNet/Configs/IConfig.cs | 2 +
.../Configs/ImmutableConfig.cs | 3 +
.../Configs/ImmutableConfigBuilder.cs | 1 +
src/BenchmarkDotNet/Configs/ManualConfig.cs | 11 ++
src/BenchmarkDotNet/Configs/WakeLockType.cs | 20 ++
.../ConsoleArguments/CommandLineOptions.cs | 4 +
.../ConsoleArguments/ConfigParser.cs | 3 +
.../Running/BenchmarkRunnerClean.cs | 1 +
.../Running/WakeLock.PInvoke.cs | 77 ++++++++
.../Running/WakeLock.SafePowerHandle.cs | 13 ++
src/BenchmarkDotNet/Running/WakeLock.cs | 44 +++++
.../PowerRequest.cs | 9 +
.../PowerRequestsParser.cs | 183 ++++++++++++++++++
.../StringExtensions.cs | 29 +++
.../WakeLockTests.cs | 182 +++++++++++++++++
.../ConfigParserTests.cs | 16 ++
.../Configs/ImmutableConfigTests.cs | 34 ++++
24 files changed, 718 insertions(+)
create mode 100644 docs/articles/samples/IntroWakeLock.md
create mode 100644 samples/BenchmarkDotNet.Samples/IntroWakeLock.cs
create mode 100644 src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs
create mode 100644 src/BenchmarkDotNet/Configs/WakeLockType.cs
create mode 100644 src/BenchmarkDotNet/Running/WakeLock.PInvoke.cs
create mode 100644 src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs
create mode 100644 src/BenchmarkDotNet/Running/WakeLock.cs
create mode 100644 tests/BenchmarkDotNet.IntegrationTests/PowerRequest.cs
create mode 100644 tests/BenchmarkDotNet.IntegrationTests/PowerRequestsParser.cs
create mode 100644 tests/BenchmarkDotNet.IntegrationTests/StringExtensions.cs
create mode 100644 tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs
diff --git a/docs/articles/guides/console-args.md b/docs/articles/guides/console-args.md
index 31b54a9137..8b79d6b506 100644
--- a/docs/articles/guides/console-args.md
+++ b/docs/articles/guides/console-args.md
@@ -243,6 +243,7 @@ dotnet run -c Release -- --filter * --runtimes net6.0 net8.0 --statisticalTest 5
* `--platform` the Platform that should be used. If not specified, the host process platform is used (default). AnyCpu/X86/X64/Arm/Arm64/LoongArch64.
* `--runOncePerIteration` run the benchmark exactly once per iteration.
* `--buildTimeout` build timeout in seconds.
+* `--preventSleep` prevents the system from entering sleep or turning off the display. No/RequireSystem/RequireDisplay.
* `--wasmEngine` full path to a java script engine used to run the benchmarks, used by Wasm toolchain.
* `--wasmMainJS` path to the test-main.js file used by Wasm toolchain. Mandatory when using \"--runtimes wasm\"
* `--expose_wasm` arguments for the JavaScript engine used by Wasm toolchain.
diff --git a/docs/articles/samples/IntroWakeLock.md b/docs/articles/samples/IntroWakeLock.md
new file mode 100644
index 0000000000..1cb3c5c96b
--- /dev/null
+++ b/docs/articles/samples/IntroWakeLock.md
@@ -0,0 +1,32 @@
+---
+uid: BenchmarkDotNet.Samples.IntroWakeLock
+---
+
+## Sample: IntroWakeLock
+
+Running Benchmarks usually takes enough time such that the system enters sleep or turns of the display.
+
+Using a WakeLock prevents the system doing so.
+
+### Source code
+
+[!code-csharp[IntroWakeLock.cs](../../../samples/BenchmarkDotNet.Samples/IntroWakeLock.cs)]
+
+### Command line
+
+```
+--preventSleep No
+```
+```
+--preventSleep RequireSystem
+```
+```
+--preventSleep RequireDisplay
+```
+
+### Links
+
+* @BenchmarkDotNet.Attributes.WakeLockAttribute
+* The permanent link to this sample: @BenchmarkDotNet.Samples.IntroWakeLock
+
+---
diff --git a/docs/articles/samples/toc.yml b/docs/articles/samples/toc.yml
index b9a1c45bf1..8dab3b5c6b 100644
--- a/docs/articles/samples/toc.yml
+++ b/docs/articles/samples/toc.yml
@@ -126,6 +126,8 @@
href: IntroTailcall.md
- name: IntroVisualStudioProfiler
href: IntroVisualStudioProfiler.md
+- name: IntroWakeLock
+ href: IntroWakeLock.md
- name: IntroWasm
href: IntroWasm.md
- name: IntroUnicode
diff --git a/samples/BenchmarkDotNet.Samples/IntroWakeLock.cs b/samples/BenchmarkDotNet.Samples/IntroWakeLock.cs
new file mode 100644
index 0000000000..136d60aba5
--- /dev/null
+++ b/samples/BenchmarkDotNet.Samples/IntroWakeLock.cs
@@ -0,0 +1,30 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using System;
+using System.Threading;
+
+// *** Attribute Style applied to Assembly ***
+[assembly: WakeLock(WakeLockType.RequireSystem)]
+
+namespace BenchmarkDotNet.Samples;
+
+// *** Attribute Style ***
+[WakeLock(WakeLockType.RequireDisplay)]
+public class IntroWakeLock
+{
+ [Benchmark]
+ public void LongRunning() => Thread.Sleep(TimeSpan.FromSeconds(10));
+}
+
+// *** Object Style ***
+[Config(typeof(Config))]
+public class IntroWakeLockObjectStyle
+{
+ private class Config : ManualConfig
+ {
+ public Config() => WakeLock = WakeLockType.RequireSystem;
+ }
+
+ [Benchmark]
+ public void LongRunning() => Thread.Sleep(TimeSpan.FromSeconds(10));
+}
diff --git a/src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs b/src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs
new file mode 100644
index 0000000000..b712f376c3
--- /dev/null
+++ b/src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs
@@ -0,0 +1,18 @@
+using BenchmarkDotNet.Configs;
+using System;
+
+namespace BenchmarkDotNet.Attributes
+{
+ ///
+ /// Placing a on your assembly or class controls whether the
+ /// system enters sleep or turns off the display while benchmarks run.
+ ///
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
+ public sealed class WakeLockAttribute : Attribute, IConfigSource
+ {
+ public WakeLockAttribute(WakeLockType wakeLockType) =>
+ Config = ManualConfig.CreateEmpty().WithWakeLock(wakeLockType);
+
+ public IConfig Config { get; }
+ }
+}
diff --git a/src/BenchmarkDotNet/Configs/DebugConfig.cs b/src/BenchmarkDotNet/Configs/DebugConfig.cs
index 0fdda7b08d..f36001d33d 100644
--- a/src/BenchmarkDotNet/Configs/DebugConfig.cs
+++ b/src/BenchmarkDotNet/Configs/DebugConfig.cs
@@ -71,6 +71,7 @@ public abstract class DebugConfig : IConfig
public SummaryStyle SummaryStyle => SummaryStyle.Default;
public ConfigUnionRule UnionRule => ConfigUnionRule.Union;
public TimeSpan BuildTimeout => DefaultConfig.Instance.BuildTimeout;
+ public WakeLockType WakeLock => DefaultConfig.Instance.WakeLock;
public string ArtifactsPath => null; // DefaultConfig.ArtifactsPath will be used if the user does not specify it in explicit way
diff --git a/src/BenchmarkDotNet/Configs/DefaultConfig.cs b/src/BenchmarkDotNet/Configs/DefaultConfig.cs
index b70333cc1d..fc874b1359 100644
--- a/src/BenchmarkDotNet/Configs/DefaultConfig.cs
+++ b/src/BenchmarkDotNet/Configs/DefaultConfig.cs
@@ -87,6 +87,8 @@ public IEnumerable GetValidators()
public TimeSpan BuildTimeout => TimeSpan.FromSeconds(120);
+ public WakeLockType WakeLock => WakeLockType.No;
+
public string ArtifactsPath
{
get
diff --git a/src/BenchmarkDotNet/Configs/IConfig.cs b/src/BenchmarkDotNet/Configs/IConfig.cs
index b311c235f5..9e3bf50ce6 100644
--- a/src/BenchmarkDotNet/Configs/IConfig.cs
+++ b/src/BenchmarkDotNet/Configs/IConfig.cs
@@ -55,6 +55,8 @@ public interface IConfig
///
TimeSpan BuildTimeout { get; }
+ public WakeLockType WakeLock { get; }
+
///
/// Collect any errors or warnings when composing the configuration
///
diff --git a/src/BenchmarkDotNet/Configs/ImmutableConfig.cs b/src/BenchmarkDotNet/Configs/ImmutableConfig.cs
index b6e03126fd..0043ecbe4b 100644
--- a/src/BenchmarkDotNet/Configs/ImmutableConfig.cs
+++ b/src/BenchmarkDotNet/Configs/ImmutableConfig.cs
@@ -56,6 +56,7 @@ internal ImmutableConfig(
SummaryStyle summaryStyle,
ConfigOptions options,
TimeSpan buildTimeout,
+ WakeLockType wakeLock,
IReadOnlyList configAnalysisConclusion)
{
columnProviders = uniqueColumnProviders;
@@ -78,6 +79,7 @@ internal ImmutableConfig(
SummaryStyle = summaryStyle;
Options = options;
BuildTimeout = buildTimeout;
+ WakeLock = wakeLock;
ConfigAnalysisConclusion = configAnalysisConclusion;
}
@@ -89,6 +91,7 @@ internal ImmutableConfig(
public ICategoryDiscoverer CategoryDiscoverer { get; }
public SummaryStyle SummaryStyle { get; }
public TimeSpan BuildTimeout { get; }
+ public WakeLockType WakeLock { get; }
public IEnumerable GetColumnProviders() => columnProviders;
public IEnumerable GetExporters() => exporters;
diff --git a/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs b/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs
index d7c0b5eb0f..f93e5590d0 100644
--- a/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs
+++ b/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs
@@ -76,6 +76,7 @@ public static ImmutableConfig Create(IConfig source)
source.SummaryStyle ?? SummaryStyle.Default,
source.Options,
source.BuildTimeout,
+ source.WakeLock,
configAnalyse.AsReadOnly()
);
}
diff --git a/src/BenchmarkDotNet/Configs/ManualConfig.cs b/src/BenchmarkDotNet/Configs/ManualConfig.cs
index 5ea1be24e9..b14f41993b 100644
--- a/src/BenchmarkDotNet/Configs/ManualConfig.cs
+++ b/src/BenchmarkDotNet/Configs/ManualConfig.cs
@@ -58,6 +58,7 @@ public class ManualConfig : IConfig
[PublicAPI] public ICategoryDiscoverer CategoryDiscoverer { get; set; }
[PublicAPI] public SummaryStyle SummaryStyle { get; set; }
[PublicAPI] public TimeSpan BuildTimeout { get; set; } = DefaultConfig.Instance.BuildTimeout;
+ [PublicAPI] public WakeLockType WakeLock { get; set; } = DefaultConfig.Instance.WakeLock;
public IReadOnlyList ConfigAnalysisConclusion => emptyConclusion;
@@ -109,6 +110,12 @@ public ManualConfig WithBuildTimeout(TimeSpan buildTimeout)
return this;
}
+ public ManualConfig WithWakeLock(WakeLockType wakeLockType)
+ {
+ WakeLock = wakeLockType;
+ return this;
+ }
+
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method will soon be removed, please start using .AddColumn() instead.")]
public void Add(params IColumn[] newColumns) => AddColumn(newColumns);
@@ -273,6 +280,7 @@ public void Add(IConfig config)
columnHidingRules.AddRange(config.GetColumnHidingRules());
Options |= config.Options;
BuildTimeout = GetBuildTimeout(BuildTimeout, config.BuildTimeout);
+ WakeLock = GetWakeLock(WakeLock, config.WakeLock);
}
///
@@ -327,5 +335,8 @@ private static TimeSpan GetBuildTimeout(TimeSpan current, TimeSpan other)
=> current == DefaultConfig.Instance.BuildTimeout
? other
: TimeSpan.FromMilliseconds(Math.Max(current.TotalMilliseconds, other.TotalMilliseconds));
+
+ private static WakeLockType GetWakeLock(WakeLockType current, WakeLockType other) =>
+ current.CompareTo(other) > 0 ? current : other;
}
}
diff --git a/src/BenchmarkDotNet/Configs/WakeLockType.cs b/src/BenchmarkDotNet/Configs/WakeLockType.cs
new file mode 100644
index 0000000000..e676c57a51
--- /dev/null
+++ b/src/BenchmarkDotNet/Configs/WakeLockType.cs
@@ -0,0 +1,20 @@
+namespace BenchmarkDotNet.Configs
+{
+ public enum WakeLockType
+ {
+ ///
+ /// Allows the system to enter sleep and/or turn off the display while benchmarks are running.
+ ///
+ No,
+
+ ///
+ /// Forces the system to be in the working state while benchmarks are running.
+ ///
+ RequireSystem,
+
+ ///
+ /// Forces the display to be on while benchmarks are running.
+ ///
+ RequireDisplay
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs b/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
index 1dcc93281a..63090f68f2 100644
--- a/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
+++ b/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
+using BenchmarkDotNet.Configs;
using BenchmarkDotNet.ConsoleArguments.ListBenchmarks;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
@@ -179,6 +180,9 @@ public bool UseDisassemblyDiagnoser
[Option("buildTimeout", Required = false, HelpText = "Build timeout in seconds.")]
public int? TimeOutInSeconds { get; set; }
+ [Option("preventSleep", Required = false, HelpText = "Prevents the system from entering sleep or turning off the display. No/RequireSystem/RequireDisplay.")]
+ public WakeLockType? WakeLock { get; set; }
+
[Option("stopOnFirstError", Required = false, Default = false, HelpText = "Stop on first error.")]
public bool StopOnFirstError { get; set; }
diff --git a/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs
index 9ec37c39eb..a58c22da8b 100644
--- a/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs
+++ b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs
@@ -392,6 +392,9 @@ private static IConfig CreateConfig(CommandLineOptions options, IConfig globalCo
if (options.TimeOutInSeconds.HasValue)
config.WithBuildTimeout(TimeSpan.FromSeconds(options.TimeOutInSeconds.Value));
+ if (options.WakeLock.HasValue)
+ config.WithWakeLock(options.WakeLock.Value);
+
return config;
}
diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs
index 6d9d48c3cc..5fb2a90e2c 100644
--- a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs
+++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs
@@ -38,6 +38,7 @@ internal static class BenchmarkRunnerClean
internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos)
{
+ using var wakeLock = WakeLock.Request(WakeLock.GetWakeLockType(benchmarkRunInfos), "BenchmarkDotNet Running Benchmarks");
using var taskbarProgress = new TaskbarProgress(TaskbarProgressState.Indeterminate);
var resolver = DefaultResolver;
diff --git a/src/BenchmarkDotNet/Running/WakeLock.PInvoke.cs b/src/BenchmarkDotNet/Running/WakeLock.PInvoke.cs
new file mode 100644
index 0000000000..9da3fd1eff
--- /dev/null
+++ b/src/BenchmarkDotNet/Running/WakeLock.PInvoke.cs
@@ -0,0 +1,77 @@
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+
+namespace BenchmarkDotNet.Running;
+
+internal partial class WakeLock
+{
+ private static class PInvoke
+ {
+ public static SafePowerHandle PowerCreateRequest(string reason)
+ {
+ REASON_CONTEXT context = new REASON_CONTEXT()
+ {
+ Version = POWER_REQUEST_CONTEXT_VERSION,
+ Flags = POWER_REQUEST_CONTEXT_FLAGS.POWER_REQUEST_CONTEXT_SIMPLE_STRING,
+ SimpleReasonString = reason
+ };
+ SafePowerHandle safePowerHandle = PowerCreateRequest(context);
+ if (safePowerHandle.IsInvalid) { throw new Win32Exception(); }
+ return safePowerHandle;
+ }
+
+ [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
+ private static extern SafePowerHandle PowerCreateRequest(REASON_CONTEXT Context);
+
+ public static void PowerSetRequest(SafePowerHandle safePowerHandle, POWER_REQUEST_TYPE requestType)
+ {
+ if (!InvokePowerSetRequest(safePowerHandle, requestType))
+ {
+ throw new Win32Exception();
+ }
+ }
+
+ [DllImport("kernel32.dll", EntryPoint = "PowerSetRequest", ExactSpelling = true, SetLastError = true)]
+ private static extern bool InvokePowerSetRequest(SafePowerHandle PowerRequest, POWER_REQUEST_TYPE RequestType);
+
+ public static void PowerClearRequest(SafePowerHandle safePowerHandle, POWER_REQUEST_TYPE requestType)
+ {
+ if (!InvokePowerClearRequest(safePowerHandle, requestType))
+ {
+ throw new Win32Exception();
+ }
+ }
+
+ [DllImport("kernel32.dll", EntryPoint = "PowerClearRequest", ExactSpelling = true, SetLastError = true)]
+ private static extern bool InvokePowerClearRequest(SafePowerHandle PowerRequest, POWER_REQUEST_TYPE RequestType);
+
+ [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
+ public static extern bool CloseHandle(nint hObject);
+
+ private struct REASON_CONTEXT
+ {
+ public uint Version;
+
+ public POWER_REQUEST_CONTEXT_FLAGS Flags;
+
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string SimpleReasonString;
+ }
+
+ private const uint POWER_REQUEST_CONTEXT_VERSION = 0U;
+
+ private enum POWER_REQUEST_CONTEXT_FLAGS : uint
+ {
+ POWER_REQUEST_CONTEXT_DETAILED_STRING = 2U,
+ POWER_REQUEST_CONTEXT_SIMPLE_STRING = 1U,
+ }
+
+ public enum POWER_REQUEST_TYPE
+ {
+ PowerRequestDisplayRequired = 0,
+ PowerRequestSystemRequired = 1,
+ PowerRequestAwayModeRequired = 2,
+ PowerRequestExecutionRequired = 3,
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs b/src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs
new file mode 100644
index 0000000000..7ddc6d2eaa
--- /dev/null
+++ b/src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs
@@ -0,0 +1,13 @@
+using Microsoft.Win32.SafeHandles;
+
+namespace BenchmarkDotNet.Running;
+
+internal partial class WakeLock
+{
+ private class SafePowerHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ private SafePowerHandle() : base(true) { }
+
+ protected override bool ReleaseHandle() => PInvoke.CloseHandle(handle);
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Running/WakeLock.cs b/src/BenchmarkDotNet/Running/WakeLock.cs
new file mode 100644
index 0000000000..12e115e2e3
--- /dev/null
+++ b/src/BenchmarkDotNet/Running/WakeLock.cs
@@ -0,0 +1,44 @@
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Detectors;
+using BenchmarkDotNet.Helpers;
+using System;
+using System.Linq;
+
+namespace BenchmarkDotNet.Running;
+
+internal partial class WakeLock
+{
+ public static WakeLockType GetWakeLockType(BenchmarkRunInfo[] benchmarkRunInfos) =>
+ benchmarkRunInfos.Select(static i => i.Config.WakeLock).Max();
+
+ public static IDisposable Request(WakeLockType wakeLockType, string reason) =>
+ wakeLockType == WakeLockType.No || !OsDetector.IsWindows() ? null : new WakeLockSentinel(wakeLockType, reason);
+
+ private class WakeLockSentinel : DisposeAtProcessTermination
+ {
+ private readonly WakeLockType wakeLockType;
+ private readonly SafePowerHandle safePowerHandle;
+
+ public WakeLockSentinel(WakeLockType wakeLockType, string reason)
+ {
+ this.wakeLockType = wakeLockType;
+ safePowerHandle = PInvoke.PowerCreateRequest(reason);
+ PInvoke.PowerSetRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestSystemRequired);
+ if (wakeLockType == WakeLockType.RequireDisplay)
+ {
+ PInvoke.PowerSetRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestDisplayRequired);
+ }
+ }
+
+ public override void Dispose()
+ {
+ if (wakeLockType == WakeLockType.RequireDisplay)
+ {
+ PInvoke.PowerClearRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestDisplayRequired);
+ }
+ PInvoke.PowerClearRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestSystemRequired);
+ safePowerHandle.Dispose();
+ base.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/BenchmarkDotNet.IntegrationTests/PowerRequest.cs b/tests/BenchmarkDotNet.IntegrationTests/PowerRequest.cs
new file mode 100644
index 0000000000..676ad23d40
--- /dev/null
+++ b/tests/BenchmarkDotNet.IntegrationTests/PowerRequest.cs
@@ -0,0 +1,9 @@
+namespace BenchmarkDotNet.IntegrationTests;
+
+internal class PowerRequest(string requestType, string requesterType, string requesterName, string? reason)
+{
+ public string RequestType { get; } = requestType;
+ public string RequesterType { get; } = requesterType;
+ public string RequesterName { get; } = requesterName;
+ public string? Reason { get; } = reason;
+}
diff --git a/tests/BenchmarkDotNet.IntegrationTests/PowerRequestsParser.cs b/tests/BenchmarkDotNet.IntegrationTests/PowerRequestsParser.cs
new file mode 100644
index 0000000000..9be168b106
--- /dev/null
+++ b/tests/BenchmarkDotNet.IntegrationTests/PowerRequestsParser.cs
@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace BenchmarkDotNet.IntegrationTests;
+
+///
+/// Parses the output of 'powercfg /requests' command into a list of s.
+///
+///
+///
+/// Not using Sprache. It is superseded by Superpower.
+/// Not using Superpower. I gained more knowledge
+/// implementing this class from scratch.
+///
+/// Example input:
+///
+/// DISPLAY:
+/// [PROCESS] \Device\HarddiskVolume3\Program Files (x86)\Google\Chrome\Application\chrome.exe
+/// Video Wake Lock
+///
+/// SYSTEM:
+/// [DRIVER] Realtek High Definition Audio(SST) ...
+/// Er wordt momenteel een audiostream gebruikt.
+/// [PROCESS] \Device\HarddiskVolume3\...\NoSleep.exe
+/// [PROCESS] \Device\HarddiskVolume3\Program Files (x86)\Google\Chrome\Application\chrome.exe
+/// Video Wake Lock
+///
+/// AWAYMODE:
+/// None.
+///
+/// EXECUTION:
+/// [PROCESS] \Device\HarddiskVolume3\Program Files (x86)\Google\Chrome\Application\chrome.exe
+/// Playing audio
+///
+/// PERFBOOST:
+/// None.
+///
+/// ACTIVELOCKSCREEN:
+/// None.
+///
+///
+///
+internal class PowerRequestsParser
+{
+ ///
+ /// Parses output of 'powercfg /requests' into a list of s.
+ ///
+ ///
+ ///
+ /// This method takes a list of s. Examines next token and decides how to
+ /// parse.
+ ///
+ ///
+ /// Output of 'powercfg /requests'.
+ public static IEnumerable Parse(string input)
+ {
+ using TokenStream tokens = new TokenStream(Tokens(input));
+ while (tokens.TryPeek().HasValue)
+ {
+ foreach (PowerRequest item in ParseRequestType(tokens))
+ {
+ yield return item;
+ }
+ }
+ }
+
+ private static IEnumerable ParseRequestType(TokenStream tokens)
+ {
+ Token requestType = tokens.Take(TokenType.RequestType);
+ if (tokens.Peek().TokenType == TokenType.RequesterType)
+ {
+ while (tokens.Peek().TokenType == TokenType.RequesterType)
+ {
+ yield return ParseRequesterType(requestType, tokens);
+ }
+ }
+ else
+ {
+ _ = tokens.Take(TokenType.None);
+ }
+ _ = tokens.Take(TokenType.EmptyLine);
+ }
+
+ private static PowerRequest ParseRequesterType(Token requestType, TokenStream tokens)
+ {
+ Token requesterType = tokens.Take(TokenType.RequesterType);
+ Token requesterName = tokens.Take(TokenType.RequesterName);
+ Token? reason = null;
+ if (tokens.Peek().TokenType == TokenType.Reason)
+ {
+ reason = tokens.Take(TokenType.Reason);
+ }
+ return new PowerRequest(requestType.Value, requesterType.Value, requesterName.Value, reason?.Value);
+ }
+
+ ///
+ /// Converts the input into a list of s.
+ ///
+ ///
+ ///
+ /// Looking at above sample, tokenizing is made simple when done line by line. Each line
+ /// contains one or two s.
+ ///
+ ///
+ /// Output of 'powercfg /requests'.
+ private static IEnumerable Tokens(string input)
+ {
+ foreach (string line in input.EnumerateLines())
+ {
+ if (line.Length == 0)
+ {
+ yield return new Token(TokenType.EmptyLine, "");
+ }
+ else if (line[line.Length - 1] == ':')
+ {
+ yield return new Token(TokenType.RequestType, line.Substring(0, line.Length - 1).ToString());
+ }
+ else if (string.Equals(line, "None.", StringComparison.InvariantCulture))
+ {
+ yield return new Token(TokenType.None, line);
+ }
+ else if (line[0] == '[')
+ {
+ int pos = line.IndexOf(']');
+ yield return new Token(TokenType.RequesterType, line.Substring(1, pos - 1));
+ yield return new Token(TokenType.RequesterName, line.Substring(pos + 2));
+ }
+ else
+ {
+ yield return new Token(TokenType.Reason, line.ToString());
+ }
+ }
+ }
+
+ ///
+ /// Adds and to an of
+ /// s.
+ ///
+ ///
+ private class TokenStream(IEnumerable tokens) : IDisposable
+ {
+ private readonly IEnumerator tokens = tokens.GetEnumerator();
+ private Token? cached;
+
+ public Token? TryPeek() => cached ??= tokens.MoveNext() ? tokens.Current : null;
+
+ public Token Peek() => TryPeek() ?? throw new EndOfStreamException();
+
+ public Token Take(TokenType requestType)
+ {
+ Token peek = Peek();
+ if (peek.TokenType == requestType)
+ {
+ cached = null;
+ return peek;
+ }
+ else
+ {
+ throw new InvalidCastException($"Unexpected Token of type '{peek.TokenType}'. Expected type '{requestType}'.");
+ }
+ }
+
+ public void Dispose() => tokens.Dispose();
+ }
+
+ private enum TokenType
+ {
+ EmptyLine,
+ None,
+ Reason,
+ RequesterName,
+ RequesterType,
+ RequestType
+ }
+
+ private readonly struct Token(TokenType tokenType, string value)
+ {
+ public TokenType TokenType { get; } = tokenType;
+
+ public string Value { get; } = value;
+ }
+}
diff --git a/tests/BenchmarkDotNet.IntegrationTests/StringExtensions.cs b/tests/BenchmarkDotNet.IntegrationTests/StringExtensions.cs
new file mode 100644
index 0000000000..41ec5f7934
--- /dev/null
+++ b/tests/BenchmarkDotNet.IntegrationTests/StringExtensions.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+
+namespace BenchmarkDotNet.IntegrationTests;
+
+internal static class StringExtensions
+{
+ private static readonly char[] newLine = Environment.NewLine.ToCharArray();
+
+ ///
+ /// Creates an array of strings by splitting this string at each occurrence of a newLine separator.
+ /// Contrary to calling value.Split('\r', '\n')
, this method does not return an empty
+ /// string when CR is followed by LF.
+ ///
+ public static IEnumerable EnumerateLines(this string value)
+ {
+ int pos;
+ while ((pos = value.IndexOfAny(newLine)) >= 0)
+ {
+ yield return value.Substring(0, pos);
+ int stride = value[pos] == '\r' && value[pos + 1] == '\n' ? 2 : 1;
+ value = value.Substring(pos + stride);
+ }
+ if (value.Length > 0)
+ {
+ yield return value;
+ }
+ }
+}
diff --git a/tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs b/tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs
new file mode 100644
index 0000000000..bfe27c8370
--- /dev/null
+++ b/tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs
@@ -0,0 +1,182 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Helpers;
+using BenchmarkDotNet.Running;
+using BenchmarkDotNet.Tests.XUnit;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.Versioning;
+using System.Security.Principal;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace BenchmarkDotNet.IntegrationTests;
+
+public class WakeLockTests(ITestOutputHelper output) : BenchmarkTestExecutor(output)
+{
+ private const string PingEventName = @"Global\WakeLockTests-ping";
+ private const string PongEventName = @"Global\WakeLockTests-pong";
+ private static readonly TimeSpan testTimeout = TimeSpan.FromMinutes(1);
+
+ [Fact]
+ public void ConfigurationDefaultValue()
+ {
+ Assert.Equal(WakeLockType.No, DefaultConfig.Instance.WakeLock);
+ Assert.Equal(WakeLockType.No, new DebugBuildConfig().WakeLock);
+ Assert.Equal(WakeLockType.No, new DebugInProcessConfig().WakeLock);
+ }
+
+ [TheoryEnvSpecific(EnvRequirement.NonWindows)]
+ [InlineData(WakeLockType.No)]
+ [InlineData(WakeLockType.RequireSystem)]
+ [InlineData(WakeLockType.RequireDisplay)]
+ public void WakeLockIsWindowsOnly(WakeLockType wakeLockType)
+ {
+ using IDisposable wakeLock = WakeLock.Request(wakeLockType, "dummy");
+ Assert.Null(wakeLock);
+ }
+
+ [FactEnvSpecific(EnvRequirement.WindowsOnly)]
+ public void WakeLockSleepOrDisplayIsAllowed()
+ {
+ using IDisposable wakeLock = WakeLock.Request(WakeLockType.No, "dummy");
+ Assert.Null(wakeLock);
+ }
+
+ [FactEnvSpecific(EnvRequirement.WindowsOnly)]
+ public void WakeLockRequireSystem()
+ {
+ using (IDisposable wakeLock = WakeLock.Request(WakeLockType.RequireSystem, "WakeLockTests"))
+ {
+ Assert.NotNull(wakeLock);
+ Assert.Equal("SYSTEM", GetPowerRequests("WakeLockTests"));
+ }
+ Assert.Equal("", GetPowerRequests());
+ }
+
+ [FactEnvSpecific(EnvRequirement.WindowsOnly)]
+ public void WakeLockRequireDisplay()
+ {
+ using (IDisposable wakeLock = WakeLock.Request(WakeLockType.RequireDisplay, "WakeLockTests"))
+ {
+ Assert.NotNull(wakeLock);
+ Assert.Equal("DISPLAY, SYSTEM", GetPowerRequests("WakeLockTests"));
+ }
+ Assert.Equal("", GetPowerRequests());
+ }
+
+ [FactEnvSpecific(EnvRequirement.NonWindows)]
+ public void BenchmarkRunnerIgnoresWakeLock() =>
+ _ = CanExecute(fullValidation: false);
+
+ [WakeLock(WakeLockType.RequireDisplay)]
+ public class IgnoreWakeLock
+ {
+ [Benchmark] public void Sleep() { }
+ }
+
+#if !NET462
+ [SupportedOSPlatform("windows")]
+#endif
+ [TheoryEnvSpecific(EnvRequirement.WindowsOnly)]
+ [InlineData(typeof(Sleepy), "")]
+ [InlineData(typeof(RequireSystem), "SYSTEM")]
+ [InlineData(typeof(RequireDisplay), "DISPLAY, SYSTEM")]
+ public async Task BenchmarkRunnerAcquiresWakeLock(Type type, string expected)
+ {
+ using EventWaitHandle
+ ping = new EventWaitHandle(false, EventResetMode.AutoReset, PingEventName),
+ pong = new EventWaitHandle(false, EventResetMode.AutoReset, PongEventName);
+ string pwrRequests = null;
+ Task task = WaitForBenchmarkRunningAndGetPowerRequests();
+ _ = CanExecute(type, fullValidation: false);
+ await task;
+
+ Assert.Equal(expected, pwrRequests);
+
+ async Task WaitForBenchmarkRunningAndGetPowerRequests()
+ {
+ await AsTask(ping, testTimeout);
+ pwrRequests = GetPowerRequests("BenchmarkDotNet Running Benchmarks");
+ pong.Set();
+ }
+ }
+
+ public class Sleepy : Base { }
+
+ [WakeLock(WakeLockType.RequireSystem)] public class RequireSystem : Base { }
+
+ [WakeLock(WakeLockType.RequireDisplay)] public class RequireDisplay : Base { }
+
+ public class Base
+ {
+ [Benchmark]
+#if !NET462
+ [SupportedOSPlatform("windows")]
+#endif
+ public void SignalBenchmarkRunningAndWaitForGetPowerRequests()
+ {
+ using EventWaitHandle
+ ping = EventWaitHandle.OpenExisting(PingEventName),
+ pong = EventWaitHandle.OpenExisting(PongEventName);
+ ping.Set();
+ pong.WaitOne(testTimeout);
+ }
+ }
+
+ private string GetPowerRequests(string? expectedReason = null)
+ {
+ Assert.True(IsAdministrator(), "'powercfg /requests' requires administrator privileges and must be executed from an elevated command prompt.");
+ string pwrRequests = ProcessHelper.RunAndReadOutput("powercfg", "/requests");
+ Output.WriteLine(pwrRequests); // Useful to analyse failing tests.
+ string fileName = Process.GetCurrentProcess().MainModule.FileName;
+ string mustEndWith = fileName.Substring(Path.GetPathRoot(fileName).Length);
+
+ return string.Join(", ",
+ from pr in PowerRequestsParser.Parse(pwrRequests)
+ where
+ pr.RequesterName.EndsWith(mustEndWith, StringComparison.InvariantCulture) &&
+ string.Equals(pr.RequesterType, "PROCESS", StringComparison.InvariantCulture) &&
+ (expectedReason == null || string.Equals(pr.Reason, expectedReason, StringComparison.InvariantCulture))
+ select pr.RequestType);
+ }
+
+ private static bool IsAdministrator()
+ {
+#if !NET462
+ // Following line prevents error CA1416: This call site is reachable on all platforms.
+ // 'WindowsIdentity.GetCurrent()' is only supported on: 'windows'.
+ Debug.Assert(OperatingSystem.IsWindows());
+#endif
+ using WindowsIdentity currentUser = WindowsIdentity.GetCurrent();
+ return new WindowsPrincipal(currentUser).IsInRole(WindowsBuiltInRole.Administrator);
+ }
+
+ private Task AsTask(WaitHandle waitHandle, TimeSpan timeout)
+ {
+ TaskCompletionSource tcs = new TaskCompletionSource();
+ RegisteredWaitHandle rwh = null;
+ rwh = ThreadPool.RegisterWaitForSingleObject(
+ waitHandle,
+ (object state, bool timedOut) =>
+ {
+ rwh.Unregister(null);
+ if (timedOut)
+ {
+ tcs.SetException(new TimeoutException());
+ }
+ else
+ {
+ tcs.SetResult(true);
+ }
+ },
+ null,
+ timeout,
+ true);
+ return tcs.Task;
+ }
+}
diff --git a/tests/BenchmarkDotNet.Tests/ConfigParserTests.cs b/tests/BenchmarkDotNet.Tests/ConfigParserTests.cs
index 0e7c439ddf..45caf9e603 100644
--- a/tests/BenchmarkDotNet.Tests/ConfigParserTests.cs
+++ b/tests/BenchmarkDotNet.Tests/ConfigParserTests.cs
@@ -364,6 +364,22 @@ public void WhenUserDoesNotSpecifyTimeoutTheDefaultValueIsUsed()
Assert.Equal(DefaultConfig.Instance.BuildTimeout, config.BuildTimeout);
}
+ [Fact]
+ public void UserCanSpecifyWakeLock()
+ {
+ var config = ConfigParser.Parse(["--preventSleep", "RequireSystem"], new OutputLogger(Output)).config;
+
+ Assert.Equal(WakeLockType.RequireSystem, config.WakeLock);
+ }
+
+ [Fact]
+ public void WhenUserDoesNotSpecifyWakeLockTheDefaultValueIsUsed()
+ {
+ var config = ConfigParser.Parse([], new OutputLogger(Output)).config;
+
+ Assert.Equal(WakeLockType.No, config.WakeLock);
+ }
+
[Theory]
[InlineData("net461")]
[InlineData("net462")]
diff --git a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs
index 2393cb737c..cdd7c06a0b 100644
--- a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs
+++ b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs
@@ -376,6 +376,40 @@ public void WhenTwoCustomTimeoutsAreProvidedTheLongerOneIsUsed(bool direction)
Assert.Equal(TimeSpan.FromSeconds(2), final.BuildTimeout);
}
+ [Fact]
+ public void WhenWakeLockIsNotSpecifiedTheDefaultValueIsUsed()
+ {
+ var mutable = ManualConfig.CreateEmpty();
+ var final = ImmutableConfigBuilder.Create(mutable);
+ Assert.Equal(DefaultConfig.Instance.WakeLock, final.WakeLock);
+ }
+
+ [Fact]
+ public void CustomWakeLockHasPrecedenceOverDefaultWakeLock()
+ {
+ WakeLockType customTimeout = WakeLockType.RequireDisplay;
+ var mutable = ManualConfig.CreateEmpty().WithWakeLock(customTimeout);
+ var final = ImmutableConfigBuilder.Create(mutable);
+ Assert.Equal(customTimeout, final.WakeLock);
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void WhenTwoCustomWakeLocksAreProvidedTheLongerOneIsUsed(bool direction)
+ {
+ var system = ManualConfig.CreateEmpty().WithWakeLock(WakeLockType.RequireSystem);
+ var display = ManualConfig.CreateEmpty().WithWakeLock(WakeLockType.RequireDisplay);
+
+ if (direction)
+ system.Add(display);
+ else
+ display.Add(system);
+
+ var final = ImmutableConfigBuilder.Create(direction ? system : display);
+ Assert.Equal(WakeLockType.RequireDisplay, final.WakeLock);
+ }
+
private static ManualConfig CreateConfigFromJobs(params Job[] jobs)
{
var config = ManualConfig.CreateEmpty();
From 75b75c98285157e05f491b187b97fe5257252d2a Mon Sep 17 00:00:00 2001
From: leonvandermeer <64834803+leonvandermeer@users.noreply.github.com>
Date: Fri, 29 Nov 2024 18:46:24 +0100
Subject: [PATCH 2/4] Apply rework from code review
Co-authored-by: Tim Cassell <35501420+timcassell@users.noreply.github.com>
---
docs/articles/guides/console-args.md | 2 +-
docs/articles/samples/IntroWakeLock.md | 8 ++--
.../BenchmarkDotNet.Samples/IntroWakeLock.cs | 6 +--
src/BenchmarkDotNet/Configs/DefaultConfig.cs | 2 +-
src/BenchmarkDotNet/Configs/ManualConfig.cs | 8 +++-
src/BenchmarkDotNet/Configs/WakeLockType.cs | 6 +--
.../ConsoleArguments/CommandLineOptions.cs | 2 +-
src/BenchmarkDotNet/Running/WakeLock.cs | 10 +++--
.../WakeLockTests.cs | 31 ++++++++-------
.../ConfigParserTests.cs | 6 +--
.../Configs/ImmutableConfigTests.cs | 39 +++++++++++--------
11 files changed, 68 insertions(+), 52 deletions(-)
diff --git a/docs/articles/guides/console-args.md b/docs/articles/guides/console-args.md
index 8b79d6b506..765ad2e2bb 100644
--- a/docs/articles/guides/console-args.md
+++ b/docs/articles/guides/console-args.md
@@ -243,7 +243,7 @@ dotnet run -c Release -- --filter * --runtimes net6.0 net8.0 --statisticalTest 5
* `--platform` the Platform that should be used. If not specified, the host process platform is used (default). AnyCpu/X86/X64/Arm/Arm64/LoongArch64.
* `--runOncePerIteration` run the benchmark exactly once per iteration.
* `--buildTimeout` build timeout in seconds.
-* `--preventSleep` prevents the system from entering sleep or turning off the display. No/RequireSystem/RequireDisplay.
+* `--wakeLock` prevents the system from entering sleep or turning off the display. None/System/Display.
* `--wasmEngine` full path to a java script engine used to run the benchmarks, used by Wasm toolchain.
* `--wasmMainJS` path to the test-main.js file used by Wasm toolchain. Mandatory when using \"--runtimes wasm\"
* `--expose_wasm` arguments for the JavaScript engine used by Wasm toolchain.
diff --git a/docs/articles/samples/IntroWakeLock.md b/docs/articles/samples/IntroWakeLock.md
index 1cb3c5c96b..6e6effacc0 100644
--- a/docs/articles/samples/IntroWakeLock.md
+++ b/docs/articles/samples/IntroWakeLock.md
@@ -4,7 +4,7 @@ uid: BenchmarkDotNet.Samples.IntroWakeLock
## Sample: IntroWakeLock
-Running Benchmarks usually takes enough time such that the system enters sleep or turns of the display.
+Running benchmarks may sometimes take enough time such that the system enters sleep or turns off the display.
Using a WakeLock prevents the system doing so.
@@ -15,13 +15,13 @@ Using a WakeLock prevents the system doing so.
### Command line
```
---preventSleep No
+--wakeLock None
```
```
---preventSleep RequireSystem
+--wakeLock System
```
```
---preventSleep RequireDisplay
+--wakeLock Display
```
### Links
diff --git a/samples/BenchmarkDotNet.Samples/IntroWakeLock.cs b/samples/BenchmarkDotNet.Samples/IntroWakeLock.cs
index 136d60aba5..099b347c7b 100644
--- a/samples/BenchmarkDotNet.Samples/IntroWakeLock.cs
+++ b/samples/BenchmarkDotNet.Samples/IntroWakeLock.cs
@@ -4,12 +4,12 @@
using System.Threading;
// *** Attribute Style applied to Assembly ***
-[assembly: WakeLock(WakeLockType.RequireSystem)]
+[assembly: WakeLock(WakeLockType.System)]
namespace BenchmarkDotNet.Samples;
// *** Attribute Style ***
-[WakeLock(WakeLockType.RequireDisplay)]
+[WakeLock(WakeLockType.Display)]
public class IntroWakeLock
{
[Benchmark]
@@ -22,7 +22,7 @@ public class IntroWakeLockObjectStyle
{
private class Config : ManualConfig
{
- public Config() => WakeLock = WakeLockType.RequireSystem;
+ public Config() => WakeLock = WakeLockType.System;
}
[Benchmark]
diff --git a/src/BenchmarkDotNet/Configs/DefaultConfig.cs b/src/BenchmarkDotNet/Configs/DefaultConfig.cs
index fc874b1359..8585a77797 100644
--- a/src/BenchmarkDotNet/Configs/DefaultConfig.cs
+++ b/src/BenchmarkDotNet/Configs/DefaultConfig.cs
@@ -87,7 +87,7 @@ public IEnumerable GetValidators()
public TimeSpan BuildTimeout => TimeSpan.FromSeconds(120);
- public WakeLockType WakeLock => WakeLockType.No;
+ public WakeLockType WakeLock => WakeLockType.System;
public string ArtifactsPath
{
diff --git a/src/BenchmarkDotNet/Configs/ManualConfig.cs b/src/BenchmarkDotNet/Configs/ManualConfig.cs
index b14f41993b..cdfb64a234 100644
--- a/src/BenchmarkDotNet/Configs/ManualConfig.cs
+++ b/src/BenchmarkDotNet/Configs/ManualConfig.cs
@@ -336,7 +336,11 @@ private static TimeSpan GetBuildTimeout(TimeSpan current, TimeSpan other)
? other
: TimeSpan.FromMilliseconds(Math.Max(current.TotalMilliseconds, other.TotalMilliseconds));
- private static WakeLockType GetWakeLock(WakeLockType current, WakeLockType other) =>
- current.CompareTo(other) > 0 ? current : other;
+ private static WakeLockType GetWakeLock(WakeLockType current, WakeLockType other)
+ {
+ if (current == DefaultConfig.Instance.WakeLock) { return other; }
+ if (other == DefaultConfig.Instance.WakeLock) { return current; }
+ return current.CompareTo(other) > 0 ? current : other;
+ }
}
}
diff --git a/src/BenchmarkDotNet/Configs/WakeLockType.cs b/src/BenchmarkDotNet/Configs/WakeLockType.cs
index e676c57a51..f547e44764 100644
--- a/src/BenchmarkDotNet/Configs/WakeLockType.cs
+++ b/src/BenchmarkDotNet/Configs/WakeLockType.cs
@@ -5,16 +5,16 @@ public enum WakeLockType
///
/// Allows the system to enter sleep and/or turn off the display while benchmarks are running.
///
- No,
+ None,
///
/// Forces the system to be in the working state while benchmarks are running.
///
- RequireSystem,
+ System,
///
/// Forces the display to be on while benchmarks are running.
///
- RequireDisplay
+ Display
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs b/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
index 63090f68f2..6c7b48b0e2 100644
--- a/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
+++ b/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
@@ -180,7 +180,7 @@ public bool UseDisassemblyDiagnoser
[Option("buildTimeout", Required = false, HelpText = "Build timeout in seconds.")]
public int? TimeOutInSeconds { get; set; }
- [Option("preventSleep", Required = false, HelpText = "Prevents the system from entering sleep or turning off the display. No/RequireSystem/RequireDisplay.")]
+ [Option("wakeLock", Required = false, HelpText = "Prevents the system from entering sleep or turning off the display. None/System/Display.")]
public WakeLockType? WakeLock { get; set; }
[Option("stopOnFirstError", Required = false, Default = false, HelpText = "Stop on first error.")]
diff --git a/src/BenchmarkDotNet/Running/WakeLock.cs b/src/BenchmarkDotNet/Running/WakeLock.cs
index 12e115e2e3..79318b282c 100644
--- a/src/BenchmarkDotNet/Running/WakeLock.cs
+++ b/src/BenchmarkDotNet/Running/WakeLock.cs
@@ -11,8 +11,12 @@ internal partial class WakeLock
public static WakeLockType GetWakeLockType(BenchmarkRunInfo[] benchmarkRunInfos) =>
benchmarkRunInfos.Select(static i => i.Config.WakeLock).Max();
+ private static readonly bool OsVersionIsSupported =
+ // Must be windows 7 or greater
+ OsDetector.IsWindows() && Environment.OSVersion.Version >= new Version(6, 1);
+
public static IDisposable Request(WakeLockType wakeLockType, string reason) =>
- wakeLockType == WakeLockType.No || !OsDetector.IsWindows() ? null : new WakeLockSentinel(wakeLockType, reason);
+ wakeLockType == WakeLockType.None || !OsVersionIsSupported ? null : new WakeLockSentinel(wakeLockType, reason);
private class WakeLockSentinel : DisposeAtProcessTermination
{
@@ -24,7 +28,7 @@ public WakeLockSentinel(WakeLockType wakeLockType, string reason)
this.wakeLockType = wakeLockType;
safePowerHandle = PInvoke.PowerCreateRequest(reason);
PInvoke.PowerSetRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestSystemRequired);
- if (wakeLockType == WakeLockType.RequireDisplay)
+ if (wakeLockType == WakeLockType.Display)
{
PInvoke.PowerSetRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestDisplayRequired);
}
@@ -32,7 +36,7 @@ public WakeLockSentinel(WakeLockType wakeLockType, string reason)
public override void Dispose()
{
- if (wakeLockType == WakeLockType.RequireDisplay)
+ if (wakeLockType == WakeLockType.Display)
{
PInvoke.PowerClearRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestDisplayRequired);
}
diff --git a/tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs b/tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs
index bfe27c8370..4d79a9b08f 100644
--- a/tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs
+++ b/tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs
@@ -25,15 +25,15 @@ public class WakeLockTests(ITestOutputHelper output) : BenchmarkTestExecutor(out
[Fact]
public void ConfigurationDefaultValue()
{
- Assert.Equal(WakeLockType.No, DefaultConfig.Instance.WakeLock);
- Assert.Equal(WakeLockType.No, new DebugBuildConfig().WakeLock);
- Assert.Equal(WakeLockType.No, new DebugInProcessConfig().WakeLock);
+ Assert.Equal(WakeLockType.System, DefaultConfig.Instance.WakeLock);
+ Assert.Equal(WakeLockType.System, new DebugBuildConfig().WakeLock);
+ Assert.Equal(WakeLockType.System, new DebugInProcessConfig().WakeLock);
}
[TheoryEnvSpecific(EnvRequirement.NonWindows)]
- [InlineData(WakeLockType.No)]
- [InlineData(WakeLockType.RequireSystem)]
- [InlineData(WakeLockType.RequireDisplay)]
+ [InlineData(WakeLockType.None)]
+ [InlineData(WakeLockType.System)]
+ [InlineData(WakeLockType.Display)]
public void WakeLockIsWindowsOnly(WakeLockType wakeLockType)
{
using IDisposable wakeLock = WakeLock.Request(wakeLockType, "dummy");
@@ -43,14 +43,14 @@ public void WakeLockIsWindowsOnly(WakeLockType wakeLockType)
[FactEnvSpecific(EnvRequirement.WindowsOnly)]
public void WakeLockSleepOrDisplayIsAllowed()
{
- using IDisposable wakeLock = WakeLock.Request(WakeLockType.No, "dummy");
+ using IDisposable wakeLock = WakeLock.Request(WakeLockType.None, "dummy");
Assert.Null(wakeLock);
}
[FactEnvSpecific(EnvRequirement.WindowsOnly)]
public void WakeLockRequireSystem()
{
- using (IDisposable wakeLock = WakeLock.Request(WakeLockType.RequireSystem, "WakeLockTests"))
+ using (IDisposable wakeLock = WakeLock.Request(WakeLockType.System, "WakeLockTests"))
{
Assert.NotNull(wakeLock);
Assert.Equal("SYSTEM", GetPowerRequests("WakeLockTests"));
@@ -61,7 +61,7 @@ public void WakeLockRequireSystem()
[FactEnvSpecific(EnvRequirement.WindowsOnly)]
public void WakeLockRequireDisplay()
{
- using (IDisposable wakeLock = WakeLock.Request(WakeLockType.RequireDisplay, "WakeLockTests"))
+ using (IDisposable wakeLock = WakeLock.Request(WakeLockType.Display, "WakeLockTests"))
{
Assert.NotNull(wakeLock);
Assert.Equal("DISPLAY, SYSTEM", GetPowerRequests("WakeLockTests"));
@@ -73,7 +73,7 @@ public void WakeLockRequireDisplay()
public void BenchmarkRunnerIgnoresWakeLock() =>
_ = CanExecute(fullValidation: false);
- [WakeLock(WakeLockType.RequireDisplay)]
+ [WakeLock(WakeLockType.Display)]
public class IgnoreWakeLock
{
[Benchmark] public void Sleep() { }
@@ -83,7 +83,8 @@ [Benchmark] public void Sleep() { }
[SupportedOSPlatform("windows")]
#endif
[TheoryEnvSpecific(EnvRequirement.WindowsOnly)]
- [InlineData(typeof(Sleepy), "")]
+ [InlineData(typeof(Default), "SYSTEM")]
+ [InlineData(typeof(None), "")]
[InlineData(typeof(RequireSystem), "SYSTEM")]
[InlineData(typeof(RequireDisplay), "DISPLAY, SYSTEM")]
public async Task BenchmarkRunnerAcquiresWakeLock(Type type, string expected)
@@ -106,11 +107,13 @@ async Task WaitForBenchmarkRunningAndGetPowerRequests()
}
}
- public class Sleepy : Base { }
+ public class Default : Base { }
- [WakeLock(WakeLockType.RequireSystem)] public class RequireSystem : Base { }
+ [WakeLock(WakeLockType.None)] public class None : Base { }
- [WakeLock(WakeLockType.RequireDisplay)] public class RequireDisplay : Base { }
+ [WakeLock(WakeLockType.System)] public class RequireSystem : Base { }
+
+ [WakeLock(WakeLockType.Display)] public class RequireDisplay : Base { }
public class Base
{
diff --git a/tests/BenchmarkDotNet.Tests/ConfigParserTests.cs b/tests/BenchmarkDotNet.Tests/ConfigParserTests.cs
index 45caf9e603..48da315cd6 100644
--- a/tests/BenchmarkDotNet.Tests/ConfigParserTests.cs
+++ b/tests/BenchmarkDotNet.Tests/ConfigParserTests.cs
@@ -367,9 +367,9 @@ public void WhenUserDoesNotSpecifyTimeoutTheDefaultValueIsUsed()
[Fact]
public void UserCanSpecifyWakeLock()
{
- var config = ConfigParser.Parse(["--preventSleep", "RequireSystem"], new OutputLogger(Output)).config;
+ var config = ConfigParser.Parse(["--wakeLock", "Display"], new OutputLogger(Output)).config;
- Assert.Equal(WakeLockType.RequireSystem, config.WakeLock);
+ Assert.Equal(WakeLockType.Display, config.WakeLock);
}
[Fact]
@@ -377,7 +377,7 @@ public void WhenUserDoesNotSpecifyWakeLockTheDefaultValueIsUsed()
{
var config = ConfigParser.Parse([], new OutputLogger(Output)).config;
- Assert.Equal(WakeLockType.No, config.WakeLock);
+ Assert.Equal(DefaultConfig.Instance.WakeLock, config.WakeLock);
}
[Theory]
diff --git a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs
index cdd7c06a0b..d77c5202ed 100644
--- a/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs
+++ b/tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs
@@ -387,27 +387,32 @@ public void WhenWakeLockIsNotSpecifiedTheDefaultValueIsUsed()
[Fact]
public void CustomWakeLockHasPrecedenceOverDefaultWakeLock()
{
- WakeLockType customTimeout = WakeLockType.RequireDisplay;
- var mutable = ManualConfig.CreateEmpty().WithWakeLock(customTimeout);
+ WakeLockType customWakeLock = WakeLockType.Display;
+ var mutable = ManualConfig.CreateEmpty().WithWakeLock(customWakeLock);
var final = ImmutableConfigBuilder.Create(mutable);
- Assert.Equal(customTimeout, final.WakeLock);
+ Assert.Equal(customWakeLock, final.WakeLock);
}
[Theory]
- [InlineData(false)]
- [InlineData(true)]
- public void WhenTwoCustomWakeLocksAreProvidedTheLongerOneIsUsed(bool direction)
- {
- var system = ManualConfig.CreateEmpty().WithWakeLock(WakeLockType.RequireSystem);
- var display = ManualConfig.CreateEmpty().WithWakeLock(WakeLockType.RequireDisplay);
-
- if (direction)
- system.Add(display);
- else
- display.Add(system);
-
- var final = ImmutableConfigBuilder.Create(direction ? system : display);
- Assert.Equal(WakeLockType.RequireDisplay, final.WakeLock);
+ [InlineData(WakeLockType.None, WakeLockType.None, WakeLockType.None)]
+ [InlineData(WakeLockType.System, WakeLockType.None, WakeLockType.None)]
+ [InlineData(WakeLockType.Display, WakeLockType.None, WakeLockType.Display)]
+ [InlineData(WakeLockType.None, WakeLockType.System, WakeLockType.None)]
+ [InlineData(WakeLockType.System, WakeLockType.System, WakeLockType.System)]
+ [InlineData(WakeLockType.Display, WakeLockType.System, WakeLockType.Display)]
+ [InlineData(WakeLockType.None, WakeLockType.Display, WakeLockType.Display)]
+ [InlineData(WakeLockType.System, WakeLockType.Display, WakeLockType.Display)]
+ [InlineData(WakeLockType.Display, WakeLockType.Display, WakeLockType.Display)]
+ public void WhenTwoCustomWakeLocksAreProvidedDisplayBeatsNoneBeatsDefault(
+ WakeLockType left, WakeLockType right, WakeLockType expected)
+ {
+ var l = ManualConfig.CreateEmpty().WithWakeLock(left);
+ var r = ManualConfig.CreateEmpty().WithWakeLock(right);
+
+ l.Add(r);
+
+ var final = ImmutableConfigBuilder.Create(l);
+ Assert.Equal(expected, final.WakeLock);
}
private static ManualConfig CreateConfigFromJobs(params Job[] jobs)
From 868a4d281fd4e97d36bb35e686dd434746cd76f5 Mon Sep 17 00:00:00 2001
From: leonvandermeer <64834803+leonvandermeer@users.noreply.github.com>
Date: Fri, 10 Jan 2025 16:11:21 +0100
Subject: [PATCH 3/4] Apply suggestions from code review
Co-authored-by: Adam Sitnik
---
docs/articles/samples/IntroWakeLock.md | 2 +-
src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs | 2 +-
src/BenchmarkDotNet/Configs/DebugConfig.cs | 2 +-
src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs | 2 +-
src/BenchmarkDotNet/Running/WakeLock.cs | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/articles/samples/IntroWakeLock.md b/docs/articles/samples/IntroWakeLock.md
index 6e6effacc0..333b3e4345 100644
--- a/docs/articles/samples/IntroWakeLock.md
+++ b/docs/articles/samples/IntroWakeLock.md
@@ -6,7 +6,7 @@ uid: BenchmarkDotNet.Samples.IntroWakeLock
Running benchmarks may sometimes take enough time such that the system enters sleep or turns off the display.
-Using a WakeLock prevents the system doing so.
+Using a WakeLock prevents the Windows system doing so.
### Source code
diff --git a/src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs b/src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs
index b712f376c3..1243ac8f8f 100644
--- a/src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs
+++ b/src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs
@@ -5,7 +5,7 @@ namespace BenchmarkDotNet.Attributes
{
///
/// Placing a on your assembly or class controls whether the
- /// system enters sleep or turns off the display while benchmarks run.
+ /// Windows system enters sleep or turns off the display while benchmarks run.
///
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
public sealed class WakeLockAttribute : Attribute, IConfigSource
diff --git a/src/BenchmarkDotNet/Configs/DebugConfig.cs b/src/BenchmarkDotNet/Configs/DebugConfig.cs
index f36001d33d..551c38fa09 100644
--- a/src/BenchmarkDotNet/Configs/DebugConfig.cs
+++ b/src/BenchmarkDotNet/Configs/DebugConfig.cs
@@ -71,7 +71,7 @@ public abstract class DebugConfig : IConfig
public SummaryStyle SummaryStyle => SummaryStyle.Default;
public ConfigUnionRule UnionRule => ConfigUnionRule.Union;
public TimeSpan BuildTimeout => DefaultConfig.Instance.BuildTimeout;
- public WakeLockType WakeLock => DefaultConfig.Instance.WakeLock;
+ public WakeLockType WakeLock => WakeLockType.None;
public string ArtifactsPath => null; // DefaultConfig.ArtifactsPath will be used if the user does not specify it in explicit way
diff --git a/src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs b/src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs
index 7ddc6d2eaa..8e6203574d 100644
--- a/src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs
+++ b/src/BenchmarkDotNet/Running/WakeLock.SafePowerHandle.cs
@@ -4,7 +4,7 @@ namespace BenchmarkDotNet.Running;
internal partial class WakeLock
{
- private class SafePowerHandle : SafeHandleZeroOrMinusOneIsInvalid
+ private sealed class SafePowerHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafePowerHandle() : base(true) { }
diff --git a/src/BenchmarkDotNet/Running/WakeLock.cs b/src/BenchmarkDotNet/Running/WakeLock.cs
index 79318b282c..ec9fd311a7 100644
--- a/src/BenchmarkDotNet/Running/WakeLock.cs
+++ b/src/BenchmarkDotNet/Running/WakeLock.cs
@@ -15,7 +15,7 @@ public static WakeLockType GetWakeLockType(BenchmarkRunInfo[] benchmarkRunInfos)
// Must be windows 7 or greater
OsDetector.IsWindows() && Environment.OSVersion.Version >= new Version(6, 1);
- public static IDisposable Request(WakeLockType wakeLockType, string reason) =>
+ public static IDisposable? Request(WakeLockType wakeLockType, string reason) =>
wakeLockType == WakeLockType.None || !OsVersionIsSupported ? null : new WakeLockSentinel(wakeLockType, reason);
private class WakeLockSentinel : DisposeAtProcessTermination
From a256b78b315cbf3017e95d24033ba58e79f87ce5 Mon Sep 17 00:00:00 2001
From: Leon van der Meer <64834803+leonvandermeer@users.noreply.github.com>
Date: Fri, 10 Jan 2025 18:30:29 +0100
Subject: [PATCH 4/4] Apply rework from code review
---
.../Running/BenchmarkRunnerClean.cs | 2 +-
src/BenchmarkDotNet/Running/WakeLock.cs | 45 ++++++++++++++-----
.../PowerRequestsParser.cs | 8 +++-
.../StringExtensions.cs | 29 ------------
.../WakeLockTests.cs | 40 +++++++----------
.../XUnit/EnvRequirement.cs | 3 +-
.../XUnit/EnvRequirementChecker.cs | 12 +++++
7 files changed, 71 insertions(+), 68 deletions(-)
delete mode 100644 tests/BenchmarkDotNet.IntegrationTests/StringExtensions.cs
diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs
index 5fb2a90e2c..4f520da38d 100644
--- a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs
+++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs
@@ -38,7 +38,6 @@ internal static class BenchmarkRunnerClean
internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos)
{
- using var wakeLock = WakeLock.Request(WakeLock.GetWakeLockType(benchmarkRunInfos), "BenchmarkDotNet Running Benchmarks");
using var taskbarProgress = new TaskbarProgress(TaskbarProgressState.Indeterminate);
var resolver = DefaultResolver;
@@ -56,6 +55,7 @@ internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos)
using (var streamLogger = new StreamLogger(GetLogFileStreamWriter(benchmarkRunInfos, logFilePath)))
{
var compositeLogger = CreateCompositeLogger(benchmarkRunInfos, streamLogger);
+ using var wakeLock = WakeLock.Request(WakeLock.GetWakeLockType(benchmarkRunInfos), "BenchmarkDotNet Running Benchmarks", streamLogger);
var eventProcessor = new CompositeEventProcessor(benchmarkRunInfos);
eventProcessor.OnStartValidationStage();
diff --git a/src/BenchmarkDotNet/Running/WakeLock.cs b/src/BenchmarkDotNet/Running/WakeLock.cs
index ec9fd311a7..a748f24626 100644
--- a/src/BenchmarkDotNet/Running/WakeLock.cs
+++ b/src/BenchmarkDotNet/Running/WakeLock.cs
@@ -1,7 +1,9 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Helpers;
+using BenchmarkDotNet.Loggers;
using System;
+using System.ComponentModel;
using System.Linq;
namespace BenchmarkDotNet.Running;
@@ -15,33 +17,52 @@ public static WakeLockType GetWakeLockType(BenchmarkRunInfo[] benchmarkRunInfos)
// Must be windows 7 or greater
OsDetector.IsWindows() && Environment.OSVersion.Version >= new Version(6, 1);
- public static IDisposable? Request(WakeLockType wakeLockType, string reason) =>
- wakeLockType == WakeLockType.None || !OsVersionIsSupported ? null : new WakeLockSentinel(wakeLockType, reason);
+ public static IDisposable? Request(WakeLockType wakeLockType, string reason, ILogger logger) =>
+ wakeLockType == WakeLockType.None || !OsVersionIsSupported ? null : new WakeLockSentinel(wakeLockType, reason, logger);
private class WakeLockSentinel : DisposeAtProcessTermination
{
private readonly WakeLockType wakeLockType;
- private readonly SafePowerHandle safePowerHandle;
+ private readonly SafePowerHandle? safePowerHandle;
+ private readonly ILogger logger;
- public WakeLockSentinel(WakeLockType wakeLockType, string reason)
+ public WakeLockSentinel(WakeLockType wakeLockType, string reason, ILogger logger)
{
this.wakeLockType = wakeLockType;
- safePowerHandle = PInvoke.PowerCreateRequest(reason);
- PInvoke.PowerSetRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestSystemRequired);
- if (wakeLockType == WakeLockType.Display)
+ this.logger = logger;
+ try
{
- PInvoke.PowerSetRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestDisplayRequired);
+ safePowerHandle = PInvoke.PowerCreateRequest(reason);
+ PInvoke.PowerSetRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestSystemRequired);
+ if (wakeLockType == WakeLockType.Display)
+ {
+ PInvoke.PowerSetRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestDisplayRequired);
+ }
+ }
+ catch (Win32Exception ex)
+ {
+ logger.WriteLineError($"Unable to prevent the system from entering sleep or turning off the display (error message: {ex.Message}).");
}
}
public override void Dispose()
{
- if (wakeLockType == WakeLockType.Display)
+ if (safePowerHandle != null)
{
- PInvoke.PowerClearRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestDisplayRequired);
+ try
+ {
+ if (wakeLockType == WakeLockType.Display)
+ {
+ PInvoke.PowerClearRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestDisplayRequired);
+ }
+ PInvoke.PowerClearRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestSystemRequired);
+ }
+ catch (Win32Exception ex)
+ {
+ logger.WriteLineError($"Unable to allow the system from entering sleep or turning off the display (error message: {ex.Message}).");
+ }
+ safePowerHandle.Dispose();
}
- PInvoke.PowerClearRequest(safePowerHandle, PInvoke.POWER_REQUEST_TYPE.PowerRequestSystemRequired);
- safePowerHandle.Dispose();
base.Dispose();
}
}
diff --git a/tests/BenchmarkDotNet.IntegrationTests/PowerRequestsParser.cs b/tests/BenchmarkDotNet.IntegrationTests/PowerRequestsParser.cs
index 9be168b106..bb634b5228 100644
--- a/tests/BenchmarkDotNet.IntegrationTests/PowerRequestsParser.cs
+++ b/tests/BenchmarkDotNet.IntegrationTests/PowerRequestsParser.cs
@@ -106,7 +106,11 @@ private static PowerRequest ParseRequesterType(Token requestType, TokenStream to
/// Output of 'powercfg /requests'.
private static IEnumerable Tokens(string input)
{
- foreach (string line in input.EnumerateLines())
+ // Contrary to calling input.Split('\r', '\n'), StringReader's ReadLine method does not
+ // return an empty string when CR is followed by LF.
+ StringReader reader = new StringReader(input);
+ string? line;
+ while ((line = reader.ReadLine()) != null)
{
if (line.Length == 0)
{
@@ -128,7 +132,7 @@ private static IEnumerable Tokens(string input)
}
else
{
- yield return new Token(TokenType.Reason, line.ToString());
+ yield return new Token(TokenType.Reason, line);
}
}
}
diff --git a/tests/BenchmarkDotNet.IntegrationTests/StringExtensions.cs b/tests/BenchmarkDotNet.IntegrationTests/StringExtensions.cs
deleted file mode 100644
index 41ec5f7934..0000000000
--- a/tests/BenchmarkDotNet.IntegrationTests/StringExtensions.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace BenchmarkDotNet.IntegrationTests;
-
-internal static class StringExtensions
-{
- private static readonly char[] newLine = Environment.NewLine.ToCharArray();
-
- ///
- /// Creates an array of strings by splitting this string at each occurrence of a newLine separator.
- /// Contrary to calling value.Split('\r', '\n')
, this method does not return an empty
- /// string when CR is followed by LF.
- ///
- public static IEnumerable EnumerateLines(this string value)
- {
- int pos;
- while ((pos = value.IndexOfAny(newLine)) >= 0)
- {
- yield return value.Substring(0, pos);
- int stride = value[pos] == '\r' && value[pos + 1] == '\n' ? 2 : 1;
- value = value.Substring(pos + stride);
- }
- if (value.Length > 0)
- {
- yield return value;
- }
- }
-}
diff --git a/tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs b/tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs
index 4d79a9b08f..cf8d0d39b9 100644
--- a/tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs
+++ b/tests/BenchmarkDotNet.IntegrationTests/WakeLockTests.cs
@@ -2,13 +2,13 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Running;
+using BenchmarkDotNet.Tests.Loggers;
using BenchmarkDotNet.Tests.XUnit;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
-using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
@@ -16,18 +16,24 @@
namespace BenchmarkDotNet.IntegrationTests;
-public class WakeLockTests(ITestOutputHelper output) : BenchmarkTestExecutor(output)
+public class WakeLockTests : BenchmarkTestExecutor
{
private const string PingEventName = @"Global\WakeLockTests-ping";
private const string PongEventName = @"Global\WakeLockTests-pong";
private static readonly TimeSpan testTimeout = TimeSpan.FromMinutes(1);
+ private readonly OutputLogger logger;
+
+ public WakeLockTests(ITestOutputHelper output) : base(output)
+ {
+ logger = new OutputLogger(Output);
+ }
[Fact]
public void ConfigurationDefaultValue()
{
Assert.Equal(WakeLockType.System, DefaultConfig.Instance.WakeLock);
- Assert.Equal(WakeLockType.System, new DebugBuildConfig().WakeLock);
- Assert.Equal(WakeLockType.System, new DebugInProcessConfig().WakeLock);
+ Assert.Equal(WakeLockType.None, new DebugBuildConfig().WakeLock);
+ Assert.Equal(WakeLockType.None, new DebugInProcessConfig().WakeLock);
}
[TheoryEnvSpecific(EnvRequirement.NonWindows)]
@@ -36,21 +42,21 @@ public void ConfigurationDefaultValue()
[InlineData(WakeLockType.Display)]
public void WakeLockIsWindowsOnly(WakeLockType wakeLockType)
{
- using IDisposable wakeLock = WakeLock.Request(wakeLockType, "dummy");
+ using IDisposable wakeLock = WakeLock.Request(wakeLockType, "dummy", logger);
Assert.Null(wakeLock);
}
[FactEnvSpecific(EnvRequirement.WindowsOnly)]
public void WakeLockSleepOrDisplayIsAllowed()
{
- using IDisposable wakeLock = WakeLock.Request(WakeLockType.None, "dummy");
+ using IDisposable wakeLock = WakeLock.Request(WakeLockType.None, "dummy", logger);
Assert.Null(wakeLock);
}
- [FactEnvSpecific(EnvRequirement.WindowsOnly)]
+ [FactEnvSpecific(EnvRequirement.WindowsOnly, EnvRequirement.NeedsPrivilegedProcess)]
public void WakeLockRequireSystem()
{
- using (IDisposable wakeLock = WakeLock.Request(WakeLockType.System, "WakeLockTests"))
+ using (IDisposable wakeLock = WakeLock.Request(WakeLockType.System, "WakeLockTests", logger))
{
Assert.NotNull(wakeLock);
Assert.Equal("SYSTEM", GetPowerRequests("WakeLockTests"));
@@ -58,10 +64,10 @@ public void WakeLockRequireSystem()
Assert.Equal("", GetPowerRequests());
}
- [FactEnvSpecific(EnvRequirement.WindowsOnly)]
+ [FactEnvSpecific(EnvRequirement.WindowsOnly, EnvRequirement.NeedsPrivilegedProcess)]
public void WakeLockRequireDisplay()
{
- using (IDisposable wakeLock = WakeLock.Request(WakeLockType.Display, "WakeLockTests"))
+ using (IDisposable wakeLock = WakeLock.Request(WakeLockType.Display, "WakeLockTests", logger))
{
Assert.NotNull(wakeLock);
Assert.Equal("DISPLAY, SYSTEM", GetPowerRequests("WakeLockTests"));
@@ -82,7 +88,7 @@ [Benchmark] public void Sleep() { }
#if !NET462
[SupportedOSPlatform("windows")]
#endif
- [TheoryEnvSpecific(EnvRequirement.WindowsOnly)]
+ [TheoryEnvSpecific(EnvRequirement.WindowsOnly, EnvRequirement.NeedsPrivilegedProcess)]
[InlineData(typeof(Default), "SYSTEM")]
[InlineData(typeof(None), "")]
[InlineData(typeof(RequireSystem), "SYSTEM")]
@@ -133,7 +139,6 @@ public void SignalBenchmarkRunningAndWaitForGetPowerRequests()
private string GetPowerRequests(string? expectedReason = null)
{
- Assert.True(IsAdministrator(), "'powercfg /requests' requires administrator privileges and must be executed from an elevated command prompt.");
string pwrRequests = ProcessHelper.RunAndReadOutput("powercfg", "/requests");
Output.WriteLine(pwrRequests); // Useful to analyse failing tests.
string fileName = Process.GetCurrentProcess().MainModule.FileName;
@@ -148,17 +153,6 @@ from pr in PowerRequestsParser.Parse(pwrRequests)
select pr.RequestType);
}
- private static bool IsAdministrator()
- {
-#if !NET462
- // Following line prevents error CA1416: This call site is reachable on all platforms.
- // 'WindowsIdentity.GetCurrent()' is only supported on: 'windows'.
- Debug.Assert(OperatingSystem.IsWindows());
-#endif
- using WindowsIdentity currentUser = WindowsIdentity.GetCurrent();
- return new WindowsPrincipal(currentUser).IsInRole(WindowsBuiltInRole.Administrator);
- }
-
private Task AsTask(WaitHandle waitHandle, TimeSpan timeout)
{
TaskCompletionSource tcs = new TaskCompletionSource();
diff --git a/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirement.cs b/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirement.cs
index 563513c6f6..79bfb5a1ae 100644
--- a/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirement.cs
+++ b/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirement.cs
@@ -7,5 +7,6 @@ public enum EnvRequirement
NonLinux,
FullFrameworkOnly,
NonFullFramework,
- DotNetCoreOnly
+ DotNetCoreOnly,
+ NeedsPrivilegedProcess
}
\ No newline at end of file
diff --git a/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirementChecker.cs b/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirementChecker.cs
index 80ea642d07..22f131455d 100644
--- a/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirementChecker.cs
+++ b/tests/BenchmarkDotNet.Tests/XUnit/EnvRequirementChecker.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
+using System.Security.Principal;
using BenchmarkDotNet.Jobs;
using JetBrains.Annotations;
using BdnRuntimeInformation = BenchmarkDotNet.Portability.RuntimeInformation;
@@ -19,8 +20,19 @@ public static class EnvRequirementChecker
EnvRequirement.FullFrameworkOnly => BdnRuntimeInformation.IsFullFramework ? null : "Full .NET Framework-only test",
EnvRequirement.NonFullFramework => !BdnRuntimeInformation.IsFullFramework ? null : "Non-Full .NET Framework test",
EnvRequirement.DotNetCoreOnly => BdnRuntimeInformation.IsNetCore ? null : ".NET/.NET Core-only test",
+ EnvRequirement.NeedsPrivilegedProcess => IsPrivilegedProcess() ? null : "Needs authorization to perform security-relevant functions",
_ => throw new ArgumentOutOfRangeException(nameof(requirement), requirement, "Unknown value")
};
+ private static bool IsPrivilegedProcess()
+ {
+#if NET462
+ using WindowsIdentity currentUser = WindowsIdentity.GetCurrent();
+ return new WindowsPrincipal(currentUser).IsInRole(WindowsBuiltInRole.Administrator);
+#else
+ return Environment.IsPrivilegedProcess;
+#endif
+ }
+
private static bool IsRuntime(RuntimeMoniker moniker) => BdnRuntimeInformation.GetCurrentRuntime().RuntimeMoniker == moniker;
}
\ No newline at end of file