Repository: dotnet/xharness Branch: main Commit: 445cc8d23b5b Files: 651 Total size: 6.5 MB Directory structure: gitextract_fjkujnb6/ ├── .azuredevops/ │ └── dependabot.yml ├── .config/ │ └── tsaoptions.json ├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .github/ │ ├── copilot-instructions.md │ └── workflows/ │ ├── backport.yml │ └── copilot-setup-steps.yml ├── .gitignore ├── Build.cmd ├── CODE_OF_CONDUCT.md ├── Directory.Build.props ├── Directory.Build.targets ├── Directory.Packages.props ├── LICENSE.TXT ├── NuGet.config ├── README.md ├── SECURITY.md ├── THIRD-PARTY-NOTICES.TXT ├── XHarness.slnx ├── azure-pipelines-public.yml ├── azure-pipelines.yml ├── build.sh ├── decoded_output.xml ├── docs/ │ ├── high-level-architecture.md │ └── integrity-check.md ├── eng/ │ ├── Publishing.props │ ├── Signing.props │ ├── Version.Details.xml │ ├── Versions.props │ ├── common/ │ │ ├── AGENTS.md │ │ ├── BuildConfiguration/ │ │ │ └── build-configuration.json │ │ ├── CIBuild.cmd │ │ ├── PSScriptAnalyzerSettings.psd1 │ │ ├── README.md │ │ ├── SetupNugetSources.ps1 │ │ ├── SetupNugetSources.sh │ │ ├── build.cmd │ │ ├── build.ps1 │ │ ├── build.sh │ │ ├── cibuild.sh │ │ ├── core-templates/ │ │ │ ├── job/ │ │ │ │ ├── job.yml │ │ │ │ ├── onelocbuild.yml │ │ │ │ ├── publish-build-assets.yml │ │ │ │ ├── renovate.yml │ │ │ │ ├── source-build.yml │ │ │ │ └── source-index-stage1.yml │ │ │ ├── jobs/ │ │ │ │ ├── jobs.yml │ │ │ │ └── source-build.yml │ │ │ ├── post-build/ │ │ │ │ ├── common-variables.yml │ │ │ │ ├── post-build.yml │ │ │ │ └── setup-maestro-vars.yml │ │ │ ├── stages/ │ │ │ │ └── renovate.yml │ │ │ ├── steps/ │ │ │ │ ├── cleanup-microbuild.yml │ │ │ │ ├── enable-internal-runtimes.yml │ │ │ │ ├── enable-internal-sources.yml │ │ │ │ ├── generate-sbom.yml │ │ │ │ ├── get-delegation-sas.yml │ │ │ │ ├── get-federated-access-token.yml │ │ │ │ ├── install-microbuild-impl.yml │ │ │ │ ├── install-microbuild.yml │ │ │ │ ├── publish-build-artifacts.yml │ │ │ │ ├── publish-logs.yml │ │ │ │ ├── publish-pipeline-artifacts.yml │ │ │ │ ├── retain-build.yml │ │ │ │ ├── send-to-helix.yml │ │ │ │ ├── source-build.yml │ │ │ │ └── source-index-stage1-publish.yml │ │ │ └── variables/ │ │ │ └── pool-providers.yml │ │ ├── cross/ │ │ │ ├── armel/ │ │ │ │ └── tizen/ │ │ │ │ └── tizen.patch │ │ │ ├── build-android-rootfs.sh │ │ │ ├── build-rootfs.sh │ │ │ ├── install-debs.py │ │ │ ├── riscv64/ │ │ │ │ └── tizen/ │ │ │ │ └── tizen.patch │ │ │ ├── tizen-build-rootfs.sh │ │ │ ├── tizen-fetch.sh │ │ │ └── toolchain.cmake │ │ ├── darc-init.ps1 │ │ ├── darc-init.sh │ │ ├── dotnet-install.cmd │ │ ├── dotnet-install.ps1 │ │ ├── dotnet-install.sh │ │ ├── dotnet.cmd │ │ ├── dotnet.ps1 │ │ ├── dotnet.sh │ │ ├── enable-cross-org-publishing.ps1 │ │ ├── generate-locproject.ps1 │ │ ├── helixpublish.proj │ │ ├── init-tools-native.cmd │ │ ├── init-tools-native.ps1 │ │ ├── init-tools-native.sh │ │ ├── internal/ │ │ │ ├── Directory.Build.props │ │ │ ├── NuGet.config │ │ │ └── Tools.csproj │ │ ├── internal-feed-operations.ps1 │ │ ├── internal-feed-operations.sh │ │ ├── loc/ │ │ │ └── P22DotNetHtmlLocalization.lss │ │ ├── msbuild.ps1 │ │ ├── msbuild.sh │ │ ├── native/ │ │ │ ├── CommonLibrary.psm1 │ │ │ ├── common-library.sh │ │ │ ├── init-compiler.sh │ │ │ ├── init-distro-rid.sh │ │ │ ├── init-os-and-arch.sh │ │ │ ├── install-cmake-test.sh │ │ │ ├── install-cmake.sh │ │ │ ├── install-dependencies.sh │ │ │ └── install-tool.ps1 │ │ ├── pipeline-logging-functions.ps1 │ │ ├── pipeline-logging-functions.sh │ │ ├── post-build/ │ │ │ ├── check-channel-consistency.ps1 │ │ │ ├── nuget-validation.ps1 │ │ │ ├── nuget-verification.ps1 │ │ │ ├── publish-using-darc.ps1 │ │ │ ├── redact-logs.ps1 │ │ │ └── symbols-validation.ps1 │ │ ├── renovate.env │ │ ├── retain-build.ps1 │ │ ├── sdk-task.ps1 │ │ ├── sdk-task.sh │ │ ├── template-guidance.md │ │ ├── templates/ │ │ │ ├── job/ │ │ │ │ ├── job.yml │ │ │ │ ├── onelocbuild.yml │ │ │ │ ├── publish-build-assets.yml │ │ │ │ ├── source-build.yml │ │ │ │ └── source-index-stage1.yml │ │ │ ├── jobs/ │ │ │ │ ├── jobs.yml │ │ │ │ └── source-build.yml │ │ │ ├── post-build/ │ │ │ │ ├── common-variables.yml │ │ │ │ ├── post-build.yml │ │ │ │ └── setup-maestro-vars.yml │ │ │ ├── steps/ │ │ │ │ ├── enable-internal-runtimes.yml │ │ │ │ ├── enable-internal-sources.yml │ │ │ │ ├── generate-sbom.yml │ │ │ │ ├── get-delegation-sas.yml │ │ │ │ ├── get-federated-access-token.yml │ │ │ │ ├── publish-build-artifacts.yml │ │ │ │ ├── publish-logs.yml │ │ │ │ ├── publish-pipeline-artifacts.yml │ │ │ │ ├── retain-build.yml │ │ │ │ ├── send-to-helix.yml │ │ │ │ ├── source-build.yml │ │ │ │ ├── source-index-stage1-publish.yml │ │ │ │ └── vmr-sync.yml │ │ │ ├── variables/ │ │ │ │ └── pool-providers.yml │ │ │ └── vmr-build-pr.yml │ │ ├── templates-official/ │ │ │ ├── job/ │ │ │ │ ├── job.yml │ │ │ │ ├── onelocbuild.yml │ │ │ │ ├── publish-build-assets.yml │ │ │ │ ├── source-build.yml │ │ │ │ └── source-index-stage1.yml │ │ │ ├── jobs/ │ │ │ │ ├── jobs.yml │ │ │ │ └── source-build.yml │ │ │ ├── post-build/ │ │ │ │ ├── common-variables.yml │ │ │ │ ├── post-build.yml │ │ │ │ └── setup-maestro-vars.yml │ │ │ ├── steps/ │ │ │ │ ├── enable-internal-runtimes.yml │ │ │ │ ├── enable-internal-sources.yml │ │ │ │ ├── generate-sbom.yml │ │ │ │ ├── get-delegation-sas.yml │ │ │ │ ├── get-federated-access-token.yml │ │ │ │ ├── publish-build-artifacts.yml │ │ │ │ ├── publish-logs.yml │ │ │ │ ├── publish-pipeline-artifacts.yml │ │ │ │ ├── retain-build.yml │ │ │ │ ├── send-to-helix.yml │ │ │ │ ├── source-build.yml │ │ │ │ └── source-index-stage1-publish.yml │ │ │ └── variables/ │ │ │ └── pool-providers.yml │ │ ├── tools.ps1 │ │ ├── tools.sh │ │ ├── vmr-sync.ps1 │ │ └── vmr-sync.sh │ ├── common-variables.yml │ ├── e2e-test.yml │ └── pipelines/ │ └── apiscan-steps.yml ├── es-metadata.yml ├── global.json ├── src/ │ ├── Microsoft.DotNet.XHarness.Android/ │ │ ├── AdbExitCodes.cs │ │ ├── AdbFailureException.cs │ │ ├── AdbRunner.cs │ │ ├── AndroidDevice.cs │ │ ├── ApkHelper.cs │ │ ├── Execution/ │ │ │ ├── AdbProcessManager.cs │ │ │ ├── AdbReportFactory.cs │ │ │ ├── Api23AndOlderReportManager.cs │ │ │ ├── IAdbProcessManager.cs │ │ │ ├── IReportManager.cs │ │ │ └── NewReportManager.cs │ │ ├── InstrumentationRunner.cs │ │ └── Microsoft.DotNet.XHarness.Android.csproj │ ├── Microsoft.DotNet.XHarness.Apple/ │ │ ├── AppOperations/ │ │ │ ├── AppInstaller.cs │ │ │ ├── AppRunner.cs │ │ │ ├── AppRunnerBase.cs │ │ │ ├── AppRunnerFactory.cs │ │ │ ├── AppTester.cs │ │ │ ├── AppTesterFactory.cs │ │ │ └── AppUninstaller.cs │ │ ├── CommunicationChannel.cs │ │ ├── CrashSnapshotReporterFactory.cs │ │ ├── Darwin.cs │ │ ├── DeviceFinder.cs │ │ ├── DeviceLogCapturerFactory.cs │ │ ├── ErrorKnowledgeBase.cs │ │ ├── ExitCodeDetector.cs │ │ ├── ILogger.cs │ │ ├── Microsoft.DotNet.XHarness.Apple.csproj │ │ └── Orchestration/ │ │ ├── BaseOrchestrator.cs │ │ ├── InstallOrchestrator.cs │ │ ├── JustRunOrchestrator.cs │ │ ├── JustTestOrchestrator.cs │ │ ├── RunOrchestrator.cs │ │ ├── SimulatorResetOrchestrator.cs │ │ ├── TestOrchestrator.cs │ │ └── UninstallOrchestrator.cs │ ├── Microsoft.DotNet.XHarness.CLI/ │ │ ├── CommandArguments/ │ │ │ ├── Android/ │ │ │ │ ├── AndroidAdbCommandArguments.cs │ │ │ │ ├── AndroidArchitecture.cs │ │ │ │ ├── AndroidDeviceCommandArguments.cs │ │ │ │ ├── AndroidInstallCommandArguments.cs │ │ │ │ ├── AndroidRunCommandArguments.cs │ │ │ │ ├── AndroidStateCommandArguments.cs │ │ │ │ ├── AndroidTestCommandArguments.cs │ │ │ │ ├── AndroidUninstallCommandArguments.cs │ │ │ │ ├── Arguments/ │ │ │ │ │ ├── ApiVersionArgument.cs │ │ │ │ │ ├── DeviceArchitectureArgument.cs │ │ │ │ │ ├── DeviceIdArgument.cs │ │ │ │ │ ├── DeviceOutputFolderArgument.cs │ │ │ │ │ ├── InstrumentationArguments.cs │ │ │ │ │ ├── InstrumentationNameArgument.cs │ │ │ │ │ ├── LaunchTimeoutArgument.cs │ │ │ │ │ ├── PackageNameArgument.cs │ │ │ │ │ ├── ShowAdbPathArgument.cs │ │ │ │ │ └── WifiArgument.cs │ │ │ │ └── IAndroidAppRunArguments.cs │ │ │ ├── AndroidHeadless/ │ │ │ │ ├── AndroidHeadlessInstallCommandArguments.cs │ │ │ │ ├── AndroidHeadlessRunCommandArguments.cs │ │ │ │ ├── AndroidHeadlessTestCommandArguments.cs │ │ │ │ ├── AndroidHeadlessUninstallCommandArguments.cs │ │ │ │ ├── Arguments/ │ │ │ │ │ ├── RuntimePathArgument.cs │ │ │ │ │ ├── TestAssemblyArgument.cs │ │ │ │ │ ├── TestPathArgument.cs │ │ │ │ │ └── TestScriptArgument.cs │ │ │ │ └── IAndroidHeadlessAppRunArguments.cs │ │ │ ├── Apple/ │ │ │ │ ├── AppleDeviceCommandsArguments.cs │ │ │ │ ├── AppleInstallCommandArguments.cs │ │ │ │ ├── AppleJustRunCommandArguments.cs │ │ │ │ ├── AppleJustTestCommandArguments.cs │ │ │ │ ├── AppleMlaunchCommandArguments.cs │ │ │ │ ├── AppleResetSimulatorCommandArguments.cs │ │ │ │ ├── AppleRunCommandArguments.cs │ │ │ │ ├── AppleStateCommandArguments.cs │ │ │ │ ├── AppleTestCommandArguments.cs │ │ │ │ ├── AppleUninstallCommandArguments.cs │ │ │ │ ├── Arguments/ │ │ │ │ │ ├── BundleIdentifierArgument.cs │ │ │ │ │ ├── CommunicationChannelArgument.cs │ │ │ │ │ ├── DeviceNameArgument.cs │ │ │ │ │ ├── EnableLldbArgument.cs │ │ │ │ │ ├── EnvironmentalVariablesArgument.cs │ │ │ │ │ ├── ForceInstallationArgument.cs │ │ │ │ │ ├── HideProgressArgument.cs │ │ │ │ │ ├── IncludeWirelessArgument.cs │ │ │ │ │ ├── LaunchTimeoutArgument.cs │ │ │ │ │ ├── ListInstalledArgument.cs │ │ │ │ │ ├── MlaunchArgument.cs │ │ │ │ │ ├── NoWaitArgument.cs │ │ │ │ │ ├── ResetSimulatorArgument.cs │ │ │ │ │ ├── ShowDevicesUUIDArgument.cs │ │ │ │ │ ├── ShowSimulatorsUUIDArgument.cs │ │ │ │ │ ├── SignalAppEndArgument.cs │ │ │ │ │ ├── TargetArgument.cs │ │ │ │ │ ├── XcodeArgument.cs │ │ │ │ │ └── XmlResultJargonArgument.cs │ │ │ │ ├── IAppleAppRunArguments.cs │ │ │ │ ├── IAppleArguments.cs │ │ │ │ └── Simulators/ │ │ │ │ ├── FindCommandArguments.cs │ │ │ │ ├── InstallCommandArguments.cs │ │ │ │ ├── ListCommandArguments.cs │ │ │ │ └── SimulatorsCommandArguments.cs │ │ │ ├── Argument.cs │ │ │ ├── Arguments/ │ │ │ │ ├── AppPathArgument.cs │ │ │ │ ├── ClassMethodFilters.cs │ │ │ │ ├── EnableCoverageArgument.cs │ │ │ │ ├── ExpectedExitCodeArgument.cs │ │ │ │ ├── OutputDirectoryArgument.cs │ │ │ │ ├── SingleMethodFilters.cs │ │ │ │ ├── TimeoutArgument.cs │ │ │ │ ├── TypeFromAssemblyArgument.cs │ │ │ │ ├── WebServerHttpEnvironmentVariables.cs │ │ │ │ ├── WebServerHttpsEnvironmentVariables.cs │ │ │ │ ├── WebServerMiddlewareArgument.cs │ │ │ │ ├── WebServerUploadResults.cs │ │ │ │ ├── WebServerUseCorsArguments.cs │ │ │ │ ├── WebServerUseCrossOriginPolicyArguments.cs │ │ │ │ ├── WebServerUseDefaultFiles.cs │ │ │ │ └── WebServerUseHttpsArguments.cs │ │ │ ├── DiagnosticsArgument.cs │ │ │ ├── HelpArgument.cs │ │ │ ├── UseJsonArgument.cs │ │ │ ├── VerbosityArgument.cs │ │ │ ├── WASI/ │ │ │ │ ├── Arguments/ │ │ │ │ │ ├── WasmEngineArgument.cs │ │ │ │ │ ├── WasmEngineArguments.cs │ │ │ │ │ └── WasmEngineLocationArgument.cs │ │ │ │ └── WasiTestCommandArguments.cs │ │ │ ├── WASM/ │ │ │ │ ├── Arguments/ │ │ │ │ │ ├── BackgroundThrottlingArgument.cs │ │ │ │ │ ├── BrowserArgument.cs │ │ │ │ │ ├── BrowserArguments.cs │ │ │ │ │ ├── BrowserLocationArgument.cs │ │ │ │ │ ├── DebuggerPortArgument.cs │ │ │ │ │ ├── ErrorPatternsFileArgument.cs │ │ │ │ │ ├── HTMLFileArgument.cs │ │ │ │ │ ├── JavaScriptEngineArgument.cs │ │ │ │ │ ├── JavaScriptEngineArguments.cs │ │ │ │ │ ├── JavaScriptEngineLocationArgument.cs │ │ │ │ │ ├── JavaScriptFileArgument.cs │ │ │ │ │ ├── LocaleArgument.cs │ │ │ │ │ ├── NoHeadlessArgument.cs │ │ │ │ │ ├── NoIncognitoArgument.cs │ │ │ │ │ ├── NoQuitArgument.cs │ │ │ │ │ ├── PageLoadStrategyArgument.cs │ │ │ │ │ ├── SymbolMapFileArgument.cs │ │ │ │ │ ├── SymbolicatePatternsFileArgument.cs │ │ │ │ │ └── SymbolicatorArgument.cs │ │ │ │ ├── IWebServerArguments.cs │ │ │ │ ├── WasmTestBrowserCommandArguments.cs │ │ │ │ ├── WasmTestCommandArguments.cs │ │ │ │ └── WebServerCommandArguments.cs │ │ │ └── XHarnessCommandArguments.cs │ │ ├── Commands/ │ │ │ ├── Android/ │ │ │ │ ├── AndroidAdbCommand.cs │ │ │ │ ├── AndroidCommand.cs │ │ │ │ ├── AndroidCommandSet.cs │ │ │ │ ├── AndroidDeviceCommand.cs │ │ │ │ ├── AndroidInstallCommand.cs │ │ │ │ ├── AndroidRunCommand.cs │ │ │ │ ├── AndroidStateCommand.cs │ │ │ │ ├── AndroidTestCommand.cs │ │ │ │ ├── AndroidUninstallCommand.cs │ │ │ │ └── IDiagnosticDataExtensions.cs │ │ │ ├── AndroidHeadless/ │ │ │ │ ├── AndroidHeadlessCommandSet.cs │ │ │ │ ├── AndroidHeadlessInstallCommand.cs │ │ │ │ ├── AndroidHeadlessRunCommand.cs │ │ │ │ ├── AndroidHeadlessTestCommand.cs │ │ │ │ └── AndroidHeadlessUninstallCommand.cs │ │ │ ├── Apple/ │ │ │ │ ├── AppleAppCommand.cs │ │ │ │ ├── AppleCommand.cs │ │ │ │ ├── AppleCommandSet.cs │ │ │ │ ├── AppleDeviceCommand.cs │ │ │ │ ├── AppleInstallCommand.cs │ │ │ │ ├── AppleJustRunCommand.cs │ │ │ │ ├── AppleJustTestCommand.cs │ │ │ │ ├── AppleMlaunchCommand.cs │ │ │ │ ├── AppleResetSimulatorCommand.cs │ │ │ │ ├── AppleRunCommand.cs │ │ │ │ ├── AppleStateCommand.cs │ │ │ │ ├── AppleTestCommand.cs │ │ │ │ ├── AppleUninstallCommand.cs │ │ │ │ └── Simulators/ │ │ │ │ ├── FindCommand.cs │ │ │ │ ├── InstallCommand.cs │ │ │ │ ├── ListCommand.cs │ │ │ │ ├── Simulator.cs │ │ │ │ ├── SimulatorsCommand.cs │ │ │ │ └── SimulatorsCommandSet.cs │ │ │ ├── GetStateCommand.cs │ │ │ ├── WASI/ │ │ │ │ ├── Engine/ │ │ │ │ │ └── WasiTestCommand.cs │ │ │ │ └── WasiCommandSet.cs │ │ │ ├── WASM/ │ │ │ │ ├── Browser/ │ │ │ │ │ ├── WasmBrowserTestRunner.cs │ │ │ │ │ └── WasmTestBrowserCommand.cs │ │ │ │ ├── ErrorPatternScanner.cs │ │ │ │ ├── JS/ │ │ │ │ │ └── WasmTestCommand.cs │ │ │ │ ├── WasmCommandSet.cs │ │ │ │ ├── WasmLogMessage.cs │ │ │ │ ├── WasmTestMessagesProcessor.cs │ │ │ │ └── WebServerCommand.cs │ │ │ ├── WebServer.cs │ │ │ ├── XHarnessCommand.cs │ │ │ ├── XHarnessHelpCommand.cs │ │ │ └── XHarnessVersionCommand.cs │ │ ├── Microsoft.DotNet.XHarness.CLI.csproj │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── Resources/ │ │ │ ├── Strings.Designer.cs │ │ │ └── Strings.resx │ │ └── XHarnessConsoleLoggerFormatter.cs │ ├── Microsoft.DotNet.XHarness.Common/ │ │ ├── CLI/ │ │ │ ├── EnvironmentVariables.cs │ │ │ ├── ExitCode.cs │ │ │ └── NoDeviceFoundException.cs │ │ ├── CommandDiagnostics.cs │ │ ├── Execution/ │ │ │ ├── IMacOSProcessManager.cs │ │ │ ├── IProcessManager.cs │ │ │ ├── LinuxProcessManager.cs │ │ │ ├── MacOSProcessManager.cs │ │ │ ├── ProcessManager.cs │ │ │ ├── ProcessManagerFactory.cs │ │ │ ├── UnixProcessManager.cs │ │ │ └── WindowsProcessManager.cs │ │ ├── Logging/ │ │ │ ├── AggregatedLog.cs │ │ │ ├── CallbackLog.cs │ │ │ ├── ConsoleLog.cs │ │ │ ├── FileBackedLog.cs │ │ │ ├── ILog.cs │ │ │ ├── Log.cs │ │ │ ├── MemoryLog.cs │ │ │ ├── NullLog.cs │ │ │ ├── ReadableLog.cs │ │ │ └── ScanLog.cs │ │ ├── Microsoft.DotNet.XHarness.Common.csproj │ │ ├── RunSummaryEmitter.cs │ │ ├── TargetPlatform.cs │ │ ├── Utilities/ │ │ │ ├── DisposableList.cs │ │ │ ├── Extensions.cs │ │ │ ├── FileUtils.cs │ │ │ └── StringUtils.cs │ │ ├── WasmSymbolicatorBase.cs │ │ └── XmlResultJargon.cs │ ├── Microsoft.DotNet.XHarness.InstrumentationBase.Xunit/ │ │ ├── DefaultAndroidEntryPoint.cs │ │ └── Microsoft.DotNet.XHarness.DefaultAndroidEntryPoint.Xunit.csproj │ ├── Microsoft.DotNet.XHarness.TestRunners.Common/ │ │ ├── AndroidApplicationEntryPointBase.cs │ │ ├── ApplicationEntryPoint.cs │ │ ├── ApplicationOptions.cs │ │ ├── CoverageManager.cs │ │ ├── Extensions.cs │ │ ├── IDevice.cs │ │ ├── IgnoreFileParser.cs │ │ ├── LogWriter.cs │ │ ├── Microsoft.DotNet.XHarness.TestRunners.Common.csproj │ │ ├── MinimumLogLevel.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── TcpTextWriter.cs │ │ ├── TestAssemblyInfo.cs │ │ ├── TestCompletionStatus.cs │ │ ├── TestExecutionState.cs │ │ ├── TestFailureInfo.cs │ │ ├── TestResult.cs │ │ ├── TestRunResult.cs │ │ ├── TestRunSelector.cs │ │ ├── TestRunSelectorType.cs │ │ ├── TestRunner.cs │ │ ├── WasmApplicationEntryPointBase.cs │ │ └── iOSApplicationEntryPointBase.cs │ ├── Microsoft.DotNet.XHarness.TestRunners.NUnit/ │ │ ├── FilterBuilder.cs │ │ ├── INUnitTestRunner.cs │ │ ├── IResultSummary.cs │ │ ├── Microsoft.DotNet.XHarness.TestRunners.NUnit.csproj │ │ ├── NUnit3XmlOutputWriter.cs │ │ ├── NUnitTestListener.cs │ │ ├── NUnitTestRunner.cs │ │ ├── OutputWriter.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── ResultSummary.cs │ │ ├── TestStatusExtensions.cs │ │ └── XmlResultJargonExtensions.cs │ ├── Microsoft.DotNet.XHarness.TestRunners.Xunit/ │ │ ├── AndroidApplicationEntryPoint.cs │ │ ├── CompletionCallbackExecutionSink.cs │ │ ├── CustomXunitTestRunner.cs │ │ ├── EnvironmentVariables.cs │ │ ├── Microsoft.DotNet.XHarness.TestRunners.Xunit.csproj │ │ ├── NUnit3Xml.xslt │ │ ├── NUnitXml.xslt │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── ReflectionBasedXunitTestRunner.cs │ │ ├── TestCaseExtensions.cs │ │ ├── ThreadlessXunitTestRunner.cs │ │ ├── WasmApplicationEntryPoint.cs │ │ ├── WasmThreadedTestRunner.cs │ │ ├── WasmXmlResultWriter.cs │ │ ├── XUnitFilter.cs │ │ ├── XUnitFilterType.cs │ │ ├── XUnitFiltersCollection.cs │ │ ├── XUnitTestRunner.cs │ │ ├── XunitTestRunnerBase.cs │ │ ├── YieldingXunitTestFrameworkExecutor.cs │ │ └── iOSApplicationEntryPoint.cs │ └── Microsoft.DotNet.XHarness.iOS.Shared/ │ ├── AppBundleInformation.cs │ ├── AppBundleInformationParser.cs │ ├── Collections/ │ │ ├── BlockingEnumerableCollection.cs │ │ └── IAsyncEnumerable.cs │ ├── CrashSnapshotReporter.cs │ ├── Execution/ │ │ ├── Arguments.cs │ │ ├── EnviromentVariables.cs │ │ ├── IMlaunchProcessManager.cs │ │ ├── MLaunchArguments.cs │ │ └── MlaunchProcessManager.cs │ ├── Extension.cs │ ├── Hardware/ │ │ ├── Device.cs │ │ ├── HardwareDeviceLoader.cs │ │ ├── IDevice.cs │ │ ├── IDeviceLoader.cs │ │ ├── IHardwareDevice.cs │ │ ├── ISimulatorDevice.cs │ │ ├── ISimulatorLoader.cs │ │ ├── SimDevicePair.cs │ │ ├── SimDeviceSpecification.cs │ │ ├── SimDeviceType.cs │ │ ├── SimRuntime.cs │ │ ├── SimulatorDevice.cs │ │ ├── SimulatorLoader.cs │ │ ├── SimulatorSelector.cs │ │ └── TCCDatabase.cs │ ├── IErrorKnowledgeBase.cs │ ├── IResultFileHandler.cs │ ├── IResultParser.cs │ ├── ITestReporter.cs │ ├── Listeners/ │ │ ├── SimpleFileListener.cs │ │ ├── SimpleHttpListener.cs │ │ ├── SimpleListener.cs │ │ ├── SimpleListenerFactory.cs │ │ ├── SimpleTcpListener.cs │ │ ├── TcpTunnel.cs │ │ └── TunnelBore.cs │ ├── Logging/ │ │ ├── AppInstallMonitorLog.cs │ │ ├── CaptureLog.cs │ │ ├── DeviceLogCapturer.cs │ │ ├── IEventLogger.cs │ │ ├── ILogs.cs │ │ ├── LogFile.cs │ │ ├── LogType.cs │ │ ├── Logs.cs │ │ └── WrenchLog.cs │ ├── Microsoft.DotNet.XHarness.iOS.Shared.csproj │ ├── ResultFileHandler.cs │ ├── RunMode.cs │ ├── SdkVersions.cs │ ├── TestExecutingResult.cs │ ├── TestReporter.cs │ ├── TestReporterFactory.cs │ ├── TestTarget.cs │ ├── Utilities/ │ │ ├── DirectoryUtilities.cs │ │ ├── Extensions.cs │ │ ├── Helpers.cs │ │ ├── PlistExtensions.cs │ │ └── ProjectFileExtensions.cs │ └── XmlResults/ │ ├── IXmlResultParser.cs │ ├── NUnitV2ResultParser.cs │ ├── NUnitV2TestReportGenerator.cs │ ├── NUnitV3ResultParser.cs │ ├── NUnitV3TestReportGenerator.cs │ ├── TestReportGenerator.cs │ ├── TouchUnitResultParser.cs │ ├── TouchUnitTestReportGenerator.cs │ ├── TrxResultParser.cs │ ├── TrxTestReportGenerator.cs │ ├── XUnitResultParser.cs │ ├── XUnitTestReportGenerator.cs │ └── XmlResultParser.cs ├── tests/ │ ├── Directory.Build.targets │ ├── Microsoft.DotNet.XHarness.Android.Tests/ │ │ ├── AdbRunnerLogFilterTests.cs │ │ ├── AdbRunnerTests.cs │ │ ├── InstrumentationRunnerSummaryTests.cs │ │ └── Microsoft.DotNet.XHarness.Android.Tests.csproj │ ├── Microsoft.DotNet.XHarness.Apple.Tests/ │ │ ├── AppOperations/ │ │ │ ├── AppInstallerTests.cs │ │ │ ├── AppRunTestBase.cs │ │ │ ├── AppRunnerTests.cs │ │ │ ├── AppTesterTests.cs │ │ │ └── AppUninstallerTests.cs │ │ ├── DeviceFinderTests.cs │ │ ├── ErrorKnowledgeBaseTests.cs │ │ ├── ExitCodeDetectorTests.cs │ │ ├── Microsoft.DotNet.XHarness.Apple.Tests.csproj │ │ ├── MockLogs.cs │ │ └── Orchestration/ │ │ ├── CopyLogsToMainLogTests.cs │ │ ├── InstallOrchestratorTests.cs │ │ ├── JustRunOrchestratorTests.cs │ │ ├── JustTestOrchestratorTests.cs │ │ ├── OrchestratorTestBase.cs │ │ ├── RunOrchestratorTests.cs │ │ ├── SimulatorResetOrchestratorTests.cs │ │ ├── TestOrchestratorTests.cs │ │ └── UninstallOrchestratorTests.cs │ ├── Microsoft.DotNet.XHarness.CLI.Tests/ │ │ ├── CommandArguments/ │ │ │ └── ArgumentTests.cs │ │ ├── Commands/ │ │ │ └── XHarnessCommandTests.cs │ │ ├── Microsoft.DotNet.XHarness.CLI.Tests.csproj │ │ ├── Resources/ │ │ │ └── StringsTests.cs │ │ ├── UnitTestArguments.cs │ │ └── UnitTestCommand.cs │ ├── Microsoft.DotNet.XHarness.Common.Tests/ │ │ ├── Execution/ │ │ │ └── ProcessManagerTests.cs │ │ ├── Logging/ │ │ │ ├── CallbackLogTest.cs │ │ │ ├── ConsoleLogTest.cs │ │ │ └── ScanLogTest.cs │ │ ├── Microsoft.DotNet.XHarness.Common.Tests.csproj │ │ └── Utilities/ │ │ └── StringUtilsTests.cs │ ├── Microsoft.DotNet.XHarness.TestRunners.Tests/ │ │ ├── CoverageManagerTests.cs │ │ ├── Microsoft.DotNet.XHarness.TestRunners.Tests.csproj │ │ ├── NUnit/ │ │ │ ├── NUnit3XmlOutputWriterTests.cs │ │ │ └── TestStatusExtensionsTests.cs │ │ └── xUnit/ │ │ ├── XUnitFilterTests.cs │ │ └── XUnitFiltersCollectionTests.cs │ ├── Microsoft.DotNet.XHarness.iOS.Shared.Tests/ │ │ ├── AppBundleInformationParserTests.cs │ │ ├── CrashSnapshotReporterTests.cs │ │ ├── Execution/ │ │ │ └── MlaunchArgumentsTests.cs │ │ ├── Hardware/ │ │ │ ├── DefaultSimulatorSelectorTests.cs │ │ │ ├── DeviceTest.cs │ │ │ ├── HardwareDeviceLoaderTests.cs │ │ │ ├── SimulatorDeviceTest.cs │ │ │ ├── SimulatorLoaderTests.cs │ │ │ └── TCCDatabaseTests.cs │ │ ├── Listeners/ │ │ │ ├── SimpleFileListenerTest.cs │ │ │ ├── SimpleListenerFactoryTest.cs │ │ │ └── SimpleTcpListenerTest.cs │ │ ├── Logging/ │ │ │ ├── CaptureLogTest.cs │ │ │ ├── LogFileTest.cs │ │ │ └── LogsTest.cs │ │ ├── Microsoft.DotNet.XHarness.iOS.Shared.Tests.csproj │ │ ├── ResultFileHandlerTests.cs │ │ ├── Samples/ │ │ │ ├── Info.plist │ │ │ ├── Issue8214.xml │ │ │ ├── Issue95.xml │ │ │ ├── MtouchArchMissingEverywhere.xml │ │ │ ├── MtouchArchMissingInConfiguration.xml │ │ │ ├── NUnitV2Sample.xml │ │ │ ├── NUnitV2SampleFailure.xml │ │ │ ├── NUnitV3Sample.xml │ │ │ ├── NUnitV3SampleFailure.xml │ │ │ ├── NUnitV3SampleFailures.xml │ │ │ ├── NUnitV3SampleParameterizedFailure.xml │ │ │ ├── NUnitV3SampleSuccess.xml │ │ │ ├── TestCaseFailures.xml │ │ │ ├── TestProject/ │ │ │ │ ├── Info.plist │ │ │ │ └── SystemXunit.csproj │ │ │ ├── TouchUnitSample.xml │ │ │ ├── TouchUnitSample2.xml │ │ │ ├── devices.xml │ │ │ ├── run-log.txt │ │ │ ├── simulators.xml │ │ │ └── xUnitSample.xml │ │ ├── TestExecutingResultTests.cs │ │ ├── TestReporterTests.cs │ │ ├── Utilities/ │ │ │ ├── PListExtensionsTests.cs │ │ │ └── ProjectFileExtensionsTests.cs │ │ └── XmlResultParserTests.cs │ └── integration-tests/ │ ├── Android/ │ │ ├── Commands.Tests.proj │ │ ├── Device.Tests.proj │ │ ├── Simulator.Tests.proj │ │ └── TestApks.proj │ ├── Apple/ │ │ ├── Device.Commands.Tests.proj │ │ ├── Device.iOS.Tests.proj │ │ ├── Device.tvOS.Tests.proj │ │ ├── Simulator.Commands.Tests.proj │ │ ├── Simulator.Scouting.Commands.Tests.proj │ │ ├── Simulator.Scouting.Tests.proj │ │ ├── Simulator.Tests.proj │ │ ├── SimulatorInstaller.Tests.proj │ │ ├── TestAppBundle.proj │ │ └── helix-payloads/ │ │ └── simulatorinstaller-integration-tests.sh │ ├── Directory.Build.props │ ├── Directory.Build.targets │ ├── README.md │ ├── Storage.props │ └── WASM/ │ └── WASM.Helix.SDK.Tests.proj └── tools/ ├── Install-XHarness.ps1 ├── install-xharness.sh ├── run-e2e-test.ps1 └── run-e2e-test.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .azuredevops/dependabot.yml ================================================ version: 2 # Disabling dependabot on Azure DevOps as this is a mirrored repo. Updates should go through github. enable-campaigned-updates: false enable-security-updates: false ================================================ FILE: .config/tsaoptions.json ================================================ { "instanceUrl": "https://devdiv.visualstudio.com/", "template": "TFSDEVDIV", "projectName": "DEVDIV", "areaPath": "DevDiv\\NET Fundamentals\\Infrastructure\\XHarness", "iterationPath": "DevDiv", "notificationAliases": [ "runtimerepo-infra@microsoft.com" ], "repositoryName":"dotnet-xharness", "codebaseName": "dotnet-xharness", "serviceTreeId": "97d8e3f4-a4c4-4c48-8ce1-d6b3342c16f1" } ================================================ FILE: .editorconfig ================================================ # editorconfig.org # top-most EditorConfig file root = true # Default settings: # A newline ending every file # Use 4 spaces as indentation [*] insert_final_newline = true indent_style = space indent_size = 4 trim_trailing_whitespace = true [project.json] indent_size = 2 # C# files [*.cs] # New line preferences csharp_new_line_before_open_brace = all csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_between_query_expression_clauses = true # Indentation preferences csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true csharp_indent_case_contents_when_block = true csharp_indent_switch_labels = true csharp_indent_labels = one_less_than_current # Modifier preferences csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion # avoid this. unless absolutely necessary dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_event = false:suggestion # Types: use keywords instead of BCL types, and permit var only when the type is clear csharp_style_var_for_built_in_types = true:silent csharp_style_var_when_type_is_apparent = true:suggestion csharp_style_var_elsewhere = false:silent csharp_style_deconstructed_variable_declaration = true:silent dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion # name all constant fields using PascalCase dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_style.pascal_case_style.capitalization = pascal_case # static fields should have s_ prefix dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected dotnet_naming_style.static_prefix_style.required_prefix = s_ dotnet_naming_style.static_prefix_style.capitalization = camel_case # internal and private fields should be _camelCase dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style dotnet_naming_symbols.private_internal_fields.applicable_kinds = field dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal dotnet_naming_style.camel_case_underscore_style.required_prefix = _ dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case # Code style defaults csharp_using_directive_placement = outside_namespace:suggestion dotnet_sort_system_directives_first = true csharp_prefer_braces = true:silent csharp_preserve_single_line_blocks = true:none csharp_preserve_single_line_statements = false:none csharp_prefer_static_local_function = true:suggestion csharp_prefer_simple_using_statement = false:none csharp_style_prefer_switch_expression = true:suggestion # Code quality dotnet_style_readonly_field = true:suggestion dotnet_code_quality_unused_parameters = non_public:suggestion # Expression-level preferences dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_auto_properties = true:suggestion dotnet_style_prefer_conditional_expression_over_assignment = true:silent dotnet_style_prefer_conditional_expression_over_return = true:silent csharp_prefer_simple_default_expression = true:suggestion # Expression-bodied members csharp_style_expression_bodied_methods = true:silent csharp_style_expression_bodied_constructors = true:silent csharp_style_expression_bodied_operators = true:silent csharp_style_expression_bodied_properties = true:silent csharp_style_expression_bodied_indexers = true:silent csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_local_functions = true:silent # Pattern matching csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_inlined_variable_declaration = true:suggestion # Null checking preferences csharp_style_throw_expression = true:suggestion csharp_style_conditional_delegate_call = true:suggestion # Other features csharp_style_prefer_index_operator = false:none csharp_style_prefer_range_operator = false:none csharp_style_pattern_local_over_anonymous_function = false:none # Space preferences csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_comma = true csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after csharp_space_around_declaration_statements = do_not_ignore csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false csharp_space_before_open_square_brackets = false csharp_space_before_semicolon_in_for_statement = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false # Analyzers dotnet_code_quality.ca1802.api_surface = private, internal csharp_style_namespace_declarations=file_scoped:suggestion # C++ Files [*.{cpp,h,in}] curly_bracket_next_line = true indent_brace_style = Allman # Xml project files [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] indent_size = 2 # Xml build files [*.builds] indent_size = 2 # Xml files [*.{xml,stylecop,resx,ruleset}] indent_size = 2 # Xml config files [*.{props,targets,config,nuspec}] indent_size = 2 # Shell scripts [*.sh] end_of_line = lf [*.{cmd, bat}] end_of_line = crlf ================================================ FILE: .git-blame-ignore-revs ================================================ # Change to file-scoped namespaces 2863beb74c1abad7ddfe9ef7afea6fc2605e8a03 ================================================ FILE: .gitattributes ================================================ # Apply LF to shell scripts automatically *.sh text eol=lf ================================================ FILE: .github/copilot-instructions.md ================================================ # XHarness Repository Copilot Instructions ## Project Overview XHarness is a .NET command-line tool that enables running xUnit/NUnit tests on mobile platforms (Android, Apple iOS/tvOS/watchOS/xrOS/Mac Catalyst), WASI, and desktop browsers (WASM). It is part of the .NET ecosystem and is essential for cross-platform testing in the .NET Foundation projects. **Key Capabilities:** - Device/emulator management and discovery - Application lifecycle management (install, run, uninstall) - Test execution and result collection in multiple formats (text, xUnit/NUnit XML) - Crash dump collection and symbolication - TCP and USB connection modes - Apple Simulator runtime installation - Integration with Helix cloud testing infrastructure ## Architecture Overview XHarness is organized into two main layers: ### Tooling Layer (src/) - **Microsoft.DotNet.XHarness.CLI** - Main CLI entry point with command definitions - **Microsoft.DotNet.XHarness.Android** - Android-specific operations using ADB - **Microsoft.DotNet.XHarness.Apple** - Apple platform operations using mlaunch - **Microsoft.DotNet.XHarness.iOS.Shared** - Apple mobile platforms shared functionality - **Microsoft.DotNet.XHarness.Common** - Core building blocks (logging, execution, utilities, diagnostics) ### Application Layer (src/) - **Microsoft.DotNet.XHarness.TestRunners.Common** - Test discovery, execution, and results aggregation - **Microsoft.DotNet.XHarness.TestRunners.Xunit** - XUnit framework integration - **Microsoft.DotNet.XHarness.TestRunners.NUnit** - NUnit framework integration - **Microsoft.DotNet.XHarness.DefaultAndroidEntryPoint.Xunit** - Default Android entry point ## Command Structure XHarness follows a platform → command pattern: ```bash xharness [platform] [command] [options] ``` ### Supported Platforms and Commands: **Android Commands:** - `AndroidTest`, `AndroidDevice`, `AndroidInstall`, `AndroidRun`, `AndroidUninstall`, `AndroidAdb`, `AndroidState` **Apple Commands:** - `AppleTest`, `AppleRun`, `AppleInstall`, `AppleUninstall`, `AppleJustTest`, `AppleJustRun`, `AppleDevice`, `AppleMlaunch`, `AppleState` **Apple Simulator Commands:** - `List`, `Find`, `Install`, `ResetSimulator` **WASM Commands:** - `WasmTest`, `WasmTestBrowser`, `WebServer` **WASI Commands:** - `WasiTest` ## Development Guidelines ### System Requirements - .NET 6+ for development and runtime - macOS with full Xcode installation for Apple scenarios - Linux/macOS/Windows for Android scenarios - Linux for browser scenarios ### Build System - Use `./build.sh` (Linux/macOS) or `Build.cmd` (Windows) for proper SDK setup - Alternative: `dotnet build XHarness.slnx` (requires correct .NET version) - Integration with Arcade SDK for .NET Foundation build standards - Azure DevOps pipelines for CI/CD ### Key Dependencies - **ADB (Android Debug Bridge)** - Required for Android operations - **mlaunch** - Required for Apple platform operations - **Helix SDK** - For cloud testing integration - Downloaded automatically during CLI build process ### Exit Codes XHarness uses standardized exit codes (see `src/Microsoft.DotNet.XHarness.Common/CLI/ExitCode.cs`): - `0` - SUCCESS - `1` - TESTS_FAILED - `70` - TIMED_OUT - `78` - PACKAGE_INSTALLATION_FAILURE - `80` - APP_CRASH - `81` - DEVICE_NOT_FOUND - And many more specific failure scenarios ## Platform-Specific Knowledge ### Android Development - Uses ADB for device communication - Supports APK installation and logcat collection - Package name-based application identification - Emulator and physical device support ### Apple Development - Uses mlaunch for device/simulator communication - Supports .app bundle and .ipa installations - Requires proper code signing and provisioning profiles - Complex simulator runtime management - TCP connection workarounds for test result streaming ### WASM/WASI Development - Browser-based test execution - WebAssembly runtime requirements - Custom web server for test hosting ## Testing Strategy ### Unit Tests - Located in `tests/` directory with platform-specific test projects - Follow naming convention: `Microsoft.DotNet.XHarness.[Component].Tests` - Use xUnit framework consistently ### Integration Tests - Located in `tests/integration-tests/` - E2E tests that use Helix cloud infrastructure - Test real device/simulator scenarios - Use `./tools/run-e2e-test.sh` for execution ### Test Runners - Applications must include TestRunner library for `apple test` command - TestRunner handles environmental variables and TCP connections - Alternative: Use `apple run` for apps without TestRunner ## Common Patterns and Conventions ### Command Implementation - Extend `XHarnessCommand` abstract base class - Implement required properties: `CommandUsage`, `CommandDescription`, `Arguments` - Use dependency injection for logging and services - Return appropriate `ExitCode` enum values ### Logging - Multiple logger types: `ConsoleLogger`, `FileLogger`, `MemoryLogger`, `AggregatedLogs`, `CallbackLogger` - Console logger is default for commands - File logger used for mlaunch and adb commands - Memory logger used by platform-specific command runners ### Error Handling - Use specific exception types in `Microsoft.DotNet.XHarness.Common.CLI` - `NoDeviceFoundException` for device discovery failures - Proper exit code mapping for different failure scenarios ### Environmental Variables - `XHARNESS_DISABLE_COLORED_OUTPUT` - Disable colored logging - `XHARNESS_LOG_WITH_TIMESTAMPS` - Enable timestamps - `XHARNESS_LOG_TEST_START` - Log test start messages - `XHARNESS_MLAUNCH_PATH` - Custom mlaunch path for development ## File and Directory Structure ``` / ├── src/ # Source code ├── tests/ # Unit and integration tests ├── docs/ # Documentation ├── eng/ # Build and engineering files ├── tools/ # Development tools and scripts ├── azure-pipelines*.yml # CI/CD pipeline definitions ├── XHarness.slnx # Main solution file ├── build.sh / Build.cmd # Build scripts └── README.md # Main documentation ``` ## Troubleshooting Guidelines ### Common Issues 1. **Apple unit tests not running**: Ensure TestRunner is included in app bundle 2. **iOS/tvOS device timeouts**: Use `--signal-app-end` flag and ensure app logs the `RUN_END_TAG` 3. **Build failures**: Check .NET SDK version and use provided build scripts 4. **Device not found**: Verify device connection and platform-specific tooling (ADB/mlaunch) ### Debugging Tips - Use appropriate verbosity levels for logging - Check device/simulator state before test execution - Verify app signing and provisioning for Apple platforms - Monitor TCP connections for test result streaming ## Development Workflow ### For Bug Fixes 1. Identify affected platform and component 2. Create unit tests to reproduce the issue 3. Implement minimal fix in appropriate layer 4. Ensure no regression in existing functionality 5. Update integration tests if needed ### For New Features 1. Understand platform-specific requirements 2. Design feature following existing command patterns 3. Implement with proper error handling and exit codes 4. Add comprehensive tests (unit and integration) 5. Update documentation and help text ### Code Quality - Follow existing naming conventions and code patterns - Use dependency injection for testability - Implement proper logging throughout - Handle platform-specific edge cases - Maintain backwards compatibility when possible ## Self-Improvement Instructions **IMPORTANT**: If you discover any issues, gaps, or outdated information in these instructions while working on XHarness issues, you must update this document with your new knowledge and learnings. This includes: 1. **New platform-specific quirks or workarounds discovered** 2. **Additional environmental variables or configuration options** 3. **Updated build procedures or dependency requirements** 4. **New testing patterns or debugging techniques** 5. **Command structure changes or new platform support** 6. **Performance optimization patterns** 7. **Security considerations or best practices** When updating these instructions: - Add specific examples and code snippets where helpful - Include version information for any platform-specific requirements - Document the context and scenario where the knowledge applies - Maintain the existing structure and organization - Test your changes to ensure accuracy Your goal is to continuously improve these instructions to become the most effective autonomous agent for XHarness development, capable of solving issues, fixing bugs, and implementing new features efficiently. --- *These instructions are designed to help you understand and work effectively with the XHarness codebase. Keep them updated as you learn more about the project.* ================================================ FILE: .github/workflows/backport.yml ================================================ name: Backport PR to branch on: issue_comment: types: [created] schedule: # once a day at 13:00 UTC to cleanup old runs - cron: '0 13 * * *' permissions: contents: write issues: write pull-requests: write actions: write jobs: backport: uses: dotnet/arcade/.github/workflows/backport-base.yml@main ================================================ FILE: .github/workflows/copilot-setup-steps.yml ================================================ # Automatically run the setup steps when they are changed to allow for easy validation, and # allow manual testing through the repository's "Actions" tab on: workflow_dispatch: push: paths: - .github/workflows/copilot-setup-steps.yml pull_request: paths: - .github/workflows/copilot-setup-steps.yml permissions: contents: read jobs: copilot-setup-steps: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Do an initial restore to ensure all dependencies are restored run: | ./eng/common/build.sh --restore - name: Put repo-local dotnet install on PATH run: | echo "PATH=$PWD/.dotnet:$PATH" >> $GITHUB_ENV - name: Check dotnet version run: | dotnet --version ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ # Local installations of .net core .dotnet/ # Locally restored packages (when simulating AzDO build) .packages/ # remove noise in mac os x .DS_Store # some ide stuff .idea .vscode/ .nuget/ ================================================ FILE: Build.cmd ================================================ @echo off powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0eng\common\Build.ps1""" -restore -build %*" exit /b %ErrorLevel% ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). ================================================ FILE: Directory.Build.props ================================================ latest MIT true true $(CopyrightNetFoundation) enable $(NetCurrent);net10.0;net9.0;net8.0 ================================================ FILE: Directory.Build.targets ================================================ ================================================ FILE: Directory.Packages.props ================================================ true true $(NoWarn);NU1507 ================================================ FILE: LICENSE.TXT ================================================ The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: NuGet.config ================================================ ================================================ FILE: README.md ================================================ # XHarness This repo contains the code to build the **XHarness dotnet tool** and a **TestRunner library** that makes running unit tests in mobile platforms easier. ## What is XHarness XHarness is primarily a command line tool that enables running xUnit like tests on Android, Apple iOS / tvOS / WatchOS / xrOS / Mac Catalyst, WASI and desktop browsers (WASM). It can - locate devices/emulators - install a given application, run it and collect results uninstalling it after, - perform the operations above as part of one command or separately if need be, - handle application crashes by collecting crash dumps (symbolicate), - use different types of connection modes (network, USB cable), - output test results in various different formats from text to xUnit/NUnit XML - install Apple Simulator runtimes (different versions of iOS, tvOS...). ## System requirements The tool requires **.NET 6** or later to be run. It is packaged as a `dotnet tool` command and can be installed using the [dotnet tool CLI](https://docs.microsoft.com/en-us/dotnet/core/tools/). - The Apple scenarios require you to run the tool on MacOS with full Xcode installation - Android scenarios are supported on Linux, macOS and Windows systems - Browsers scenarios are supported on Linux systems ## Try the tool out quickly If you want to test the tool quickly, following script will install the required .NET SDK and the XHarness tool locally in the current folder. ```bash # Using bash on Linux/MacOS curl -L https://aka.ms/get-xharness | bash - ``` ```powershell # Using PowerShell on Windows iex ((New-Object System.Net.WebClient).DownloadString('https://aka.ms/get-xharness-ps1')) ``` You can delete the folder after you're done, nothing is installed in your system. ## Installation and usage To install the latest version of the tool run (in bash): ```bash dotnet tool install Microsoft.DotNet.XHarness.CLI \ --global \ --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json \ --version "11.0.0-prerelease*" ``` Or run (in PowerShell): ```powershell dotnet tool install Microsoft.DotNet.XHarness.CLI ` --global ` --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json ` --version "11.0.0-prerelease*" ``` You can get a specific version from [the dotnet-eng feed](https://dev.azure.com/dnceng/public/_packaging?_a=package&feed=dotnet-eng&view=versions&package=Microsoft.DotNet.XHarness.CLI&protocolType=NuGet) where it is published. So far, we are in preview so omitting the version will fail to locate a stable version of the tool and fail the installation so a specific version has to be supplied. To run the tool, use the `xharness` command. The tool returns one of the exit codes [listed here (ExitCode.cs)](https://github.com/dotnet/xharness/blob/main/src/Microsoft.DotNet.XHarness.Common/CLI/ExitCode.cs). The tool always expects the platform (`android`/`apple`/`browser`) followed by a command. To get an up-to-date set of commands, please run `xharness help`. > Applications run via the `apple test` command require a TestRunner inside of the iOS/tvOS app bundle to work properly. The `apple run` command, on the other hand, doesn't expect the TestRunner and only runs the application and tries to detect the exit code. Detection of exit code might not work across different iOS versions reliably. > > **\*** See the [Test Runners section](#test-runners). Example: ```bash xharness android state ``` To list all the possible commands, use the `help` command: ```bash xharness help ``` To get help for a specific command or sub-command, run: ```bash xharness help apple xharness help apple test ``` ### Other settings There are other settings which can be controlled via **environmental variables** and are primarily meant for build pipeline scenarios: - `XHARNESS_DISABLE_COLORED_OUTPUT` - disable colored logging so that control characters are not making the logs hard to read - `XHARNESS_LOG_WITH_TIMESTAMPS` - enable timestamps for logging - `XHARNESS_LOG_TEST_START` - log test start messages, useful to diagnose when tests are hanging. Currently only works for WebAssembly - `XHARNESS_MLAUNCH_PATH` - local path to the mlaunch binary when developing XHarness (when not using as .NET tool) ### Arcade/Helix integration In case your repository is onboarded into [Arcade](https://github.com/dotnet/arcade) you can use the [Arcade Helix SDK](https://github.com/dotnet/arcade/tree/master/src/Microsoft.DotNet.Helix/Sdk) to run XHarness jobs over Helix. More on how to do that is described [here](https://github.com/dotnet/arcade/blob/master/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/Readme.md). ## Examples To run an iOS/tvOS app bundle on a 64bit iPhone Simulator: ```bash xharness apple test \ --app=/path/to/an.app \ --output-directory=out \ --target=ios-simulator-64 ``` or the same can be achieved via the shorthand versions of the same options: ```bash xharness apple test -a=/path/to/an.app -o=out -t=ios-simulator-64 ``` The `out` dir will then contain log files such as these: ```console iPhone X (iOS 13.3) - created by xharness.log # logs from the Simulator test-Simulator_iOS64.log # logs from the tool itself test-ios-simulator-64-20200430_025916.xml # test results in XML format ``` Example for Android apk: ```bash xharness android test \ --output-directory=out \ --package-name=net.dot.System.Numerics.Vectors.Tests \ --app=/path/to/test.apk ``` Output directory will have a file with dump from logcat and a file with tests results. ## Test Runners The repository also contains several TestRunners which are libraries that can be bundled inside of the application and execute the tests. The TestRunner detects and executes unit tests inside of the application. It also connects to XHarness over TCP connection from within the running app bundle and reports test run results/state. There is a library `Microsoft.DotNet.XHarness.DefaultAndroidEntryPoint.Xunit` that provides default logic for Android test app entry point. It is possible to use `DefaultAndroidEntryPoint` from there for the test app by providing only test result path and test assemblies. Other parameters can be overrided as well if needed. Currently we support Xunit and NUnit test assemblies but the `Microsoft.DotNet.XHarness.Tests.Runners` supports implementation of custom runner too. ## Development instructions When working on XHarness, there are couple of neat hacks that can improve the inner loop. The repository can either be built using regular .NET, assuming you have new enough version: ``` dotnet build XHarness.slnx ``` or you can use the build scripts `build.sh` or `Build.cmd` in repository root which will install the correct .NET SDK into the `.dotnet` folder. You can then use ``` ./.dotnet/dotnet build XHarness.slnx ``` You can also use Visual Studio 2019+ and just F5 the `Microsoft.DotNet.XHarness.CLI` project. ### ADB, mlaunch In order for XHarness to work, you will need ADB (for Android) and mlaunch (for anything Apple). These are executables that go with the packaged .NET xharness tool. The easiest way to get these at the moment for development purposes is to build the CLI project and they will be downloaded. ``` dotnet build src/Microsoft.DotNet.XHarness.CLI/Microsoft.DotNet.XHarness.CLI.csproj ``` You can then find these dependencies in `artifacts/obj/Microsoft.DotNet.XHarness.CLI/`. For iOS flows, you can further store the path to mlaunch to an environmental variable `XHARNESS_MLAUNCH_PATH` ``` export XHARNESS_MLAUNCH_PATH='[xharness root]/artifacts/obj/Microsoft.DotNet.XHarness.CLI/mlaunch/bin/mlaunch' ``` and you won't have to specify the `--mlaunch` argument. ### Running E2E tests In case you want to test your changes in XHarness, you can run E2E tests located in `/tests/integration-tests`. These usually download some pre-built application and send it to our "test cloud" called Helix together with an XHarness version built from your sources. There, XHarness executes the app on a device/simulator. To run the E2E tests, you can find a script in `tools/` that will build everything and create the cloud job for you: ``` ./tools/run-e2e-test.sh Apple/Simulator.Tests.proj ``` ## Troubleshooting Some XHarness commands only work in some scenarios and it's good to know what to expect from the tool. Some Android/Apple versions also require some workarounds and those are also good to know about. ### My Apple unit tests are not running For the `apple test` command, XHarness expects the application to contain a `TestRunner` which is a library you can find in this repository. This library executes unit tests similarly how you would execute them on other platforms. However, the `TestRunner` from this repository contains more mechanisms that help to work around some issues (mostly in Apple platforms). The way it works is that XHarness usually sets some [environmental variables](https://github.com/dotnet/xharness/blob/main/src/Microsoft.DotNet.XHarness.iOS.Shared/Execution/EnviromentVariables.cs) for the application and the [`TestRunner` recognizes them](https://github.com/dotnet/xharness/blob/main/src/Microsoft.DotNet.XHarness.TestRunners.Common/ApplicationOptions.cs) and acts upon them. The workarounds we talk about are for example some TCP connections between the app and XHarness so that we can stream back the test results. For these reasons, the `test` command won't just work with any app. For those scenarios, use the `apple run` commands. ### iOS/tvOS device runs are timing out For some iOS/tvOS, we have problems detecting when the application exits on the real device (simulators work fine). The workaround we went with lies in sharing a random string with the application using an [environmental variable `RUN_END_TAG`](https://github.com/dotnet/xharness/blob/main/src/Microsoft.DotNet.XHarness.iOS.Shared/Execution/EnviromentVariables.cs) and expecting the app to output this string at the end of its run. To turn this workaround on, run XHarness with `--signal-app-end` and make sure your application logs the string it reads from the env variable. Using the `TestRunner` from this repository will automatically give you this functionality. ## Contribution We welcome contributions! Please follow the [Code of Conduct](CODE_OF_CONDUCT.md). ## Filing issues This repo should contain issues that are tied to the XHarness command line tool and the TestRunners. For other issues, please use the following repos: - For .NET runtime and Base Class Library issues, file in the [dotnet/runtime](https://github.com/dotnet/runtime) repo - For overall .NET SDK issues, file in the [dotnet/sdk](https://github.com/dotnet/sdk) repo ## License .NET (including the xharness repo) is licensed under the [MIT](LICENSE.TXT) license. ================================================ FILE: SECURITY.md ================================================ ## Security Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. ## Preferred Languages We prefer all communications to be in English. ## Policy Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). ================================================ FILE: THIRD-PARTY-NOTICES.TXT ================================================ .NET Core uses third-party libraries or other resources that may be distributed under licenses different than the .NET Core software. In the event that we accidentally failed to list a required notice, please bring it to our attention. Post an issue or email us: dotnet@microsoft.com The attached notices are provided for information only. No notices are provided at this time. ================================================ FILE: XHarness.slnx ================================================ ================================================ FILE: azure-pipelines-public.yml ================================================ variables: - template: /eng/common-variables.yml - template: /eng/common/templates/variables/pool-providers.yml # CI and PR triggers trigger: batch: true branches: include: - main - release/* pr: branches: include: - main - release/* # Build stages: - stage: Build_Windows_NT displayName: Build Windows jobs: - template: /eng/common/templates/jobs/jobs.yml parameters: enableTelemetry: true enablePublishBuildArtifacts: true enableMicrobuild: true publishingVersion: 4 enablePublishBuildAssets: true helixRepo: dotnet/xharness jobs: - job: Windows_NT enablePublishing: true pool: name: $(DncEngPublicBuildPool) demands: ImageOverride -equals 1es-windows-2022-open strategy: matrix: Release: _BuildConfig: Release _PublishArgs: '' Debug: _BuildConfig: Debug _PublishArgs: /p:Publish=false steps: - script: eng\common\CIBuild.cmd -configuration $(_BuildConfig) -prepareMachine $(_PublishArgs) name: Build displayName: Build and run tests condition: succeeded() - task: PublishTestResults@2 displayName: 'Publish Unit Test Results' inputs: testResultsFormat: xUnit testResultsFiles: '$(Build.SourcesDirectory)/artifacts/TestResults/**/*.xml' mergeTestResults: true searchFolder: $(system.defaultworkingdirectory) testRunTitle: XHarness unit tests - $(Agent.JobName) condition: succeededOrFailed() - stage: Build_OSX displayName: Build OSX dependsOn: jobs: - template: /eng/common/templates/jobs/jobs.yml parameters: enableTelemetry: true enablePublishBuildArtifacts: true enableMicrobuild: true publishingVersion: 4 enablePublishBuildAssets: true helixRepo: dotnet/xharness jobs: - job: OSX enablePublishing: true pool: vmImage: macOS-15 strategy: matrix: Release: _BuildConfig: Release _PublishArgs: '' Debug: _BuildConfig: Debug _PublishArgs: /p:Publish=false steps: - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine $(_PublishArgs) name: Build displayName: Build and run tests condition: succeeded() - publish: $(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/Shipping/Microsoft.DotNet.XHarness.CLI.11.0.0-ci.nupkg artifact: Microsoft.DotNet.XHarness.CLI.$(_BuildConfig) displayName: Publish XHarness CLI for Helix Testing condition: and(succeeded(), eq(variables['_BuildConfig'], 'Debug')) - task: PublishTestResults@2 displayName: 'Publish Unit Test Results' inputs: testResultsFormat: xUnit testResultsFiles: '$(Build.SourcesDirectory)/artifacts/TestResults/**/*.xml' mergeTestResults: true searchFolder: $(system.defaultworkingdirectory) testRunTitle: XHarness unit tests - $(Agent.JobName) condition: succeededOrFailed() # E2E tests - template: eng/e2e-test.yml parameters: name: E2E_Android_Simulators displayName: Android - Simulators testProject: $(Build.SourcesDirectory)/tests/integration-tests/Android/Simulator.Tests.proj - template: eng/e2e-test.yml parameters: name: E2E_Android_Devices displayName: Android - Devices testProject: $(Build.SourcesDirectory)/tests/integration-tests/Android/Device.Tests.proj - template: eng/e2e-test.yml parameters: name: E2E_Android_Manual_Commands displayName: Android - Manual Commands testProject: $(Build.SourcesDirectory)/tests/integration-tests/Android/Commands.Tests.proj - template: eng/e2e-test.yml parameters: name: E2E_Apple_Simulators displayName: Apple - Simulators testProject: $(Build.SourcesDirectory)/tests/integration-tests/Apple/Simulator.Tests.proj - template: eng/e2e-test.yml parameters: name: E2E_iOS_Devices displayName: Apple - iOS devices testProject: $(Build.SourcesDirectory)/tests/integration-tests/Apple/Device.iOS.Tests.proj - template: eng/e2e-test.yml parameters: name: E2E_tvOS_Devices displayName: Apple - tvOS devices testProject: $(Build.SourcesDirectory)/tests/integration-tests/Apple/Device.tvOS.Tests.proj - template: eng/e2e-test.yml parameters: name: E2E_Apple_Simulator_Commands displayName: Apple - Simulator Commands testProject: $(Build.SourcesDirectory)/tests/integration-tests/Apple/Simulator.Commands.Tests.proj - template: eng/e2e-test.yml parameters: name: E2E_Apple_Device_Commands displayName: Apple - Device Commands testProject: $(Build.SourcesDirectory)/tests/integration-tests/Apple/Device.Commands.Tests.proj - template: eng/e2e-test.yml parameters: name: E2E_Apple_Simulator_Mgmt displayName: Apple - Simulator management testProject: $(Build.SourcesDirectory)/tests/integration-tests/Apple/SimulatorInstaller.Tests.proj - template: eng/e2e-test.yml parameters: name: E2E_WASM displayName: WASM testProject: $(Build.SourcesDirectory)/tests/integration-tests/WASM/WASM.Helix.SDK.Tests.proj ================================================ FILE: azure-pipelines.yml ================================================ variables: - template: /eng/common-variables.yml@self - template: /eng/common/templates-official/variables/pool-providers.yml@self # CI triggers trigger: batch: true branches: include: - main - release/* - internal/release/* pr: none # Scheduled trigger - runs once per week on Sunday at midnight UTC schedules: - cron: '0 0 * * 0' displayName: Weekly build branches: include: - main always: true resources: repositories: - repository: 1ESPipelineTemplates type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates parameters: settings: networkIsolationPolicy: Permissive,CFSClean,CFSClean2 sdl: tsa: enabled: true policheck: enabled: true binskim: enabled: true codeql: ${{ if startsWith(variables['Build.SourceBranch'], 'refs/heads/release/') }}: enabledOnNonDefaultBranches: true pool: name: $(DncEngInternalBuildPool) image: 1es-windows-2022 os: windows stages: - stage: Build jobs: - template: /eng/common/templates-official/jobs/jobs.yml@self parameters: enableTelemetry: true enablePublishBuildArtifacts: true enableMicrobuild: true publishingVersion: 4 ${{ if ne(variables['Build.Reason'], 'Schedule') }}: enablePublishBuildAssets: true helixRepo: dotnet/xharness jobs: - job: Windows_NT enablePublishing: true displayName: Build Windows timeoutInMinutes: 120 steps: - script: eng\common\CIBuild.cmd -configuration $(_BuildConfig) -prepareMachine $(_InternalBuildArgs) /p:Test=false name: Build displayName: Build condition: succeeded() - template: /eng/pipelines/apiscan-steps.yml templateContext: outputs: - output: pipelineArtifact artifact: Artifacts_Windows_NT path: '$(Build.SourcesDirectory)\artifacts\bin\Microsoft.DotNet.XHarness.CLI' displayName: 'Publish Windows_NT Artifacts' condition: succeeded() - ${{ if ne(variables['Build.Reason'], 'Schedule') }}: - template: /eng/common/templates-official/post-build/post-build.yml@self parameters: publishingInfraVersion: 4 enableSymbolValidation: true enableSourceLinkValidation: true ================================================ FILE: build.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" # resolve $SOURCE until the file is no longer a symlink while [[ -h $source ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" "$scriptroot/eng/common/build.sh" --build --restore $@ ================================================ FILE: decoded_output.xml ================================================ ================================================ FILE: docs/high-level-architecture.md ================================================ # Overview This document provides a high-level overview of the XHarness architecture, which is organized into two main layers: the tooling layer and the application layer. ## Tooling layer The tooling layer represents the foundation of the tool. **XHarness.CLI** is the CLI that defines commands and arguments for interacting with XHarness. It serves as the main entry point for users. Supported platforms include Android, Apple mobile, MacCatalyst, WASI, and desktop browsers (WASM). The available commands are: ### Android commands: - `AndroidTest` - `AndroidDevice` - `AndroidInstall` - `AndroidRun` - `AndroidUninstall` - `AndroidAdb` - `AndroidState` ### Apple mobile commands: - `AppleTest` - `AppleRun` - `AppleInstall` - `AppleUninstall` - `AppleJustTest` - `AppleJustRun` - `AppleDevice` - `AppleMlaunch` - `AppleState` ### Apple simulator commands: - `List` - `Find` - `Install` - `ResetSimulator` ### WASM commands: - `WasmTest` - `WasmTestBrowser` - `WebServer` ### WASI commands: - `WasiTest` **XHarness.Android** and **XHarness.Apple** implement platform-specific operations and their orchestration. On Android, it uses `adb` runner for communication with devices and command execution. For Apple mobile platforms, it relies on `mlaunch` manager to interact with simulators and devices. **XHarness.iOS.Shared** is specific to Apple mobile platforms, managing functionalities such as AppBundle information, simulators, and device configurations. **XHarness.Common** provides the essential building blocks, including logging, execution, utilities, and diagnostics. Logging supports various formats, such as `ConsoleLogger`, `FileLogger`, `MemoryLogger`, `AggregatedLogs`, and `CallbackLogger`. The execution component implements platform-specific command runners. Utilities contain helper functions used across the tool. Diagnostics provide detailed information about the execution environment. Logging is enabled by default, with the console logger being the default for commands. The file logger is used for `mlaunch` and `adb` commands. The memory logger is used by the platform-specific command runners. ## Application layer The application layer manages test execution, discovery, and results aggregation. **XHarness.TestRunners.XUnit** and **XHarness.TestRunners.NUnit** are framework-specific components designed to run tests using these frameworks. **XHarness.TestRunners.Common** implements the tests discovery, execution, and results aggregation. ![High-level overview of the XHarness architecture](xharness-overview.svg) ================================================ FILE: docs/integrity-check.md ================================================ # Integrity check of 3rd party dependencies ## NuGet dependencies All NuGet dependencies are checked and verified by Component Governance tool as part of the official build in the 1ES production pipeline. ## Android platform tools Android platform tools are distributed as part of the XHarness.CLI NuGet package. The tools and required dependencies are downloaded from the official Google repository (as documented in: https://developer.android.com/studio/releases/platform-tools) and embedded in the XHarness.CLI NuGet package for each supported host operating system. - NOTE: Example URL used for downloading specific platform tools version for Windows: https://dl.google.com/android/repository/platform-tools_r30.0.5-windows.zip This dependency is listed in the SBOM manifest generated during the official build of XHarness.CLI NuGet package. ## Apple iOS simulator runtimes XHarness enables users to download and install a specific iOS simulator runtime (available through `xharness apple simulators install ` command). Invoking the said command will: - Fetch information about the available simulator runtimes from Apple repository - Download the desired simulator runtime image - Install/Mount the desired simulator runtime image Regarding integrity check all the above operations are using: - If Xcode version < 16: - Official Apple sources: https://devimages-cdn.apple.com/downloads/xcode/simulators/index2.dvtdownloadableindex to acquire simulator image information like image build number, file size, download URL and similar - Official download URLs acquired from the previous step to download runtime images (example: https://download.developer.apple.com/Developer_Tools/iOS_17.5_Simulator_Runtime/iOS_17.5_Simulator_Runtime.dmg) - Download request uses ADC cookies - Downloaded image payload is compared against the specified file size - Official Apple tooling which performs package verification prior to installation/mounting: - `xcrun simctl runtime add ` performs the following by default (from the official documentation `xcrun simctl runtime --help`): ```bash Add a runtime disk image to the secure storage area. The image will be staged, verified, and mounted. ``` - `hdiutil attach ` performs the following by default (from the official documentation https://ss64.com/mac/hdiutil.html): ```bash By default, hdiutil attach attempts to intelligently verify images that contain checksums before attaching them. ``` - If Xcode version >= 16.0 - Official Apple tooling to download/install/mount simulator runtime images through `xcode -downloadPlatform iOS` (source: https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes) ================================================ FILE: eng/Publishing.props ================================================ 4 false ================================================ FILE: eng/Signing.props ================================================ ================================================ FILE: eng/Version.Details.xml ================================================ https://github.com/dotnet/arcade 3f6871376906201d8237b9c0167fd4a844098566 https://github.com/dotnet/arcade 3f6871376906201d8237b9c0167fd4a844098566 ================================================ FILE: eng/Versions.props ================================================ 11.0.0 prerelease true ================================================ FILE: eng/common/AGENTS.md ================================================ # `eng/common` Files under `eng/common` come from [Arcade](https://github.com/dotnet/arcade). Edits in `eng/common` will be overwritten by automation unless the changes are made directly in the Arcade repository. For more information, see the [Arcade documentation](https://github.com/dotnet/arcade/tree/main/Documentation). ================================================ FILE: eng/common/BuildConfiguration/build-configuration.json ================================================ { "RetryCountLimit": 1, "RetryByAnyError": false } ================================================ FILE: eng/common/CIBuild.cmd ================================================ @echo off powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -publish -ci %*" ================================================ FILE: eng/common/PSScriptAnalyzerSettings.psd1 ================================================ @{ IncludeRules=@('PSAvoidUsingCmdletAliases', 'PSAvoidUsingWMICmdlet', 'PSAvoidUsingPositionalParameters', 'PSAvoidUsingInvokeExpression', 'PSUseDeclaredVarsMoreThanAssignments', 'PSUseCmdletCorrectly', 'PSStandardDSCFunctionsInResource', 'PSUseIdenticalMandatoryParametersForDSC', 'PSUseIdenticalParametersForDSC') } ================================================ FILE: eng/common/README.md ================================================ # Don't touch this folder uuuuuuuuuuuuuuuuuuuu u" uuuuuuuuuuuuuuuuuu "u u" u$$$$$$$$$$$$$$$$$$$$u "u u" u$$$$$$$$$$$$$$$$$$$$$$$$u "u u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ $ $$$" ... "$... ...$" ... "$$$ ... "$$$ $ $ $$$u `"$$$$$$$ $$$ $$$$$ $$ $$$ $$$ $ $ $$$$$$uu "$$$$ $$$ $$$$$ $$ """ u$$$ $ $ $$$""$$$ $$$$ $$$u "$$$" u$$ $$$$$$$$ $ $ $$$$....,$$$$$..$$$$$....,$$$$..$$$$$$$$ $ $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" "u "$$$$$$$$$$$$$$$$$$$$$$$$" u" "u "$$$$$$$$$$$$$$$$$$$$" u" "u """""""""""""""""" u" """""""""""""""""""" !!! Changes made in this directory are subject to being overwritten by automation !!! The files in this directory are shared by all Arcade repos and managed by automation. If you need to make changes to these files, open an issue or submit a pull request to https://github.com/dotnet/arcade first. ================================================ FILE: eng/common/SetupNugetSources.ps1 ================================================ # This script adds internal feeds required to build commits that depend on internal package sources. For instance, # dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. In addition also enables # disabled internal Maestro (darc-int*) feeds. # # Optionally, this script also adds a credential entry for each of the internal feeds if supplied. # # See example call for this script below. # # - task: PowerShell@2 # displayName: Setup internal Feeds Credentials # condition: eq(variables['Agent.OS'], 'Windows_NT') # inputs: # filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1 # arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $Env:Token # env: # Token: $(dn-bot-dnceng-artifact-feeds-rw) # # Note that the NuGetAuthenticate task should be called after SetupNugetSources. # This ensures that: # - Appropriate creds are set for the added internal feeds (if not supplied to the scrupt) # - The credential provider is installed. # # This logic is also abstracted into enable-internal-sources.yml. [CmdletBinding()] param ( [Parameter(Mandatory = $true)][string]$ConfigFile, $Password ) $ErrorActionPreference = "Stop" Set-StrictMode -Version 2.0 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 . $PSScriptRoot\tools.ps1 # Adds or enables the package source with the given name function AddOrEnablePackageSource($sources, $disabledPackageSources, $SourceName, $SourceEndPoint, $creds, $Username, $pwd) { if ($disabledPackageSources -eq $null -or -not (EnableInternalPackageSource -DisabledPackageSources $disabledPackageSources -Creds $creds -PackageSourceName $SourceName)) { AddPackageSource -Sources $sources -SourceName $SourceName -SourceEndPoint $SourceEndPoint -Creds $creds -Username $userName -pwd $Password } } # Add source entry to PackageSources function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Username, $pwd) { $packageSource = $sources.SelectSingleNode("add[@key='$SourceName']") if ($packageSource -eq $null) { Write-Host "Adding package source $SourceName" $packageSource = $doc.CreateElement("add") $packageSource.SetAttribute("key", $SourceName) $packageSource.SetAttribute("value", $SourceEndPoint) $sources.AppendChild($packageSource) | Out-Null } else { Write-Host "Package source $SourceName already present and enabled." } AddCredential -Creds $creds -Source $SourceName -Username $Username -pwd $pwd } # Add a credential node for the specified source function AddCredential($creds, $source, $username, $pwd) { # If no cred supplied, don't do anything. if (!$pwd) { return; } Write-Host "Inserting credential for feed: " $source # Looks for credential configuration for the given SourceName. Create it if none is found. $sourceElement = $creds.SelectSingleNode($Source) if ($sourceElement -eq $null) { $sourceElement = $doc.CreateElement($Source) $creds.AppendChild($sourceElement) | Out-Null } # Add the node to the credential if none is found. $usernameElement = $sourceElement.SelectSingleNode("add[@key='Username']") if ($usernameElement -eq $null) { $usernameElement = $doc.CreateElement("add") $usernameElement.SetAttribute("key", "Username") $sourceElement.AppendChild($usernameElement) | Out-Null } $usernameElement.SetAttribute("value", $Username) # Add the to the credential if none is found. # Add it as a clear text because there is no support for encrypted ones in non-windows .Net SDKs. # -> https://github.com/NuGet/Home/issues/5526 $passwordElement = $sourceElement.SelectSingleNode("add[@key='ClearTextPassword']") if ($passwordElement -eq $null) { $passwordElement = $doc.CreateElement("add") $passwordElement.SetAttribute("key", "ClearTextPassword") $sourceElement.AppendChild($passwordElement) | Out-Null } $passwordElement.SetAttribute("value", $pwd) } # Enable all darc-int package sources. function EnableMaestroInternalPackageSources($DisabledPackageSources, $Creds) { $maestroInternalSources = $DisabledPackageSources.SelectNodes("add[contains(@key,'darc-int')]") ForEach ($DisabledPackageSource in $maestroInternalSources) { EnableInternalPackageSource -DisabledPackageSources $DisabledPackageSources -Creds $Creds -PackageSourceName $DisabledPackageSource.key } } # Enables an internal package source by name, if found. Returns true if the package source was found and enabled, false otherwise. function EnableInternalPackageSource($DisabledPackageSources, $Creds, $PackageSourceName) { $DisabledPackageSource = $DisabledPackageSources.SelectSingleNode("add[@key='$PackageSourceName']") if ($DisabledPackageSource) { Write-Host "Enabling internal source '$($DisabledPackageSource.key)'." # Due to https://github.com/NuGet/Home/issues/10291, we must actually remove the disabled entries $DisabledPackageSources.RemoveChild($DisabledPackageSource) AddCredential -Creds $creds -Source $DisabledPackageSource.Key -Username $userName -pwd $Password return $true } return $false } if (!(Test-Path $ConfigFile -PathType Leaf)) { Write-PipelineTelemetryError -Category 'Build' -Message "Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" ExitWithExitCode 1 } # Load NuGet.config $doc = New-Object System.Xml.XmlDocument $filename = (Get-Item $ConfigFile).FullName $doc.Load($filename) # Get reference to - fail if none exist $sources = $doc.DocumentElement.SelectSingleNode("packageSources") if ($sources -eq $null) { Write-PipelineTelemetryError -Category 'Build' -Message "Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. NuGet config file must contain a packageSources section: $ConfigFile" ExitWithExitCode 1 } $creds = $null $feedSuffix = "v3/index.json" if ($Password) { $feedSuffix = "v2" # Looks for a node. Create it if none is found. $creds = $doc.DocumentElement.SelectSingleNode("packageSourceCredentials") if ($creds -eq $null) { $creds = $doc.CreateElement("packageSourceCredentials") $doc.DocumentElement.AppendChild($creds) | Out-Null } } $userName = "dn-bot" # Check for disabledPackageSources; we'll enable any darc-int ones we find there $disabledSources = $doc.DocumentElement.SelectSingleNode("disabledPackageSources") if ($disabledSources -ne $null) { Write-Host "Checking for any darc-int disabled package sources in the disabledPackageSources node" EnableMaestroInternalPackageSources -DisabledPackageSources $disabledSources -Creds $creds } $dotnetVersions = @('5','6','7','8','9','10') foreach ($dotnetVersion in $dotnetVersions) { $feedPrefix = "dotnet" + $dotnetVersion; $dotnetSource = $sources.SelectSingleNode("add[@key='$feedPrefix']") if ($dotnetSource -ne $null) { AddOrEnablePackageSource -Sources $sources -DisabledPackageSources $disabledSources -SourceName "$feedPrefix-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal/nuget/$feedSuffix" -Creds $creds -Username $userName -pwd $Password AddOrEnablePackageSource -Sources $sources -DisabledPackageSources $disabledSources -SourceName "$feedPrefix-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal-transport/nuget/$feedSuffix" -Creds $creds -Username $userName -pwd $Password } } $doc.Save($filename) ================================================ FILE: eng/common/SetupNugetSources.sh ================================================ #!/usr/bin/env bash # This script adds internal feeds required to build commits that depend on internal package sources. For instance, # dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. In addition also enables # disabled internal Maestro (darc-int*) feeds. # # Optionally, this script also adds a credential entry for each of the internal feeds if supplied. # # See example call for this script below. # # - task: Bash@3 # displayName: Setup Internal Feeds # inputs: # filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.sh # arguments: $(System.DefaultWorkingDirectory)/NuGet.config # condition: ne(variables['Agent.OS'], 'Windows_NT') # - task: NuGetAuthenticate@1 # # Note that the NuGetAuthenticate task should be called after SetupNugetSources. # This ensures that: # - Appropriate creds are set for the added internal feeds (if not supplied to the scrupt) # - The credential provider is installed. # # This logic is also abstracted into enable-internal-sources.yml. ConfigFile=$1 CredToken=$2 NL='\n' TB=' ' source="${BASH_SOURCE[0]}" # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . "$scriptroot/tools.sh" if [ ! -f "$ConfigFile" ]; then Write-PipelineTelemetryError -Category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" ExitWithExitCode 1 fi if [[ `uname -s` == "Darwin" ]]; then NL=$'\\\n' TB='' fi # Enables an internal package source by name, if found. Returns 0 if found and enabled, 1 if not found. EnableInternalPackageSource() { local PackageSourceName="$1" # Check if disabledPackageSources section exists grep -i "" "$ConfigFile" > /dev/null if [ "$?" != "0" ]; then return 1 # No disabled sources section fi # Check if this source name is disabled grep -i " /dev/null if [ "$?" == "0" ]; then echo "Enabling internal source '$PackageSourceName'." # Remove the disabled entry (including any surrounding comments or whitespace on the same line) sed -i.bak "//d" "$ConfigFile" # Add the source name to PackageSources for credential handling PackageSources+=("$PackageSourceName") return 0 # Found and enabled fi return 1 # Not found in disabled sources } # Add source entry to PackageSources AddPackageSource() { local SourceName="$1" local SourceEndPoint="$2" # Check if source already exists grep -i " /dev/null if [ "$?" == "0" ]; then echo "Package source $SourceName already present and enabled." PackageSources+=("$SourceName") return fi echo "Adding package source $SourceName" PackageSourcesNodeFooter="" PackageSourceTemplate="${TB}" sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" "$ConfigFile" PackageSources+=("$SourceName") } # Adds or enables the package source with the given name AddOrEnablePackageSource() { local SourceName="$1" local SourceEndPoint="$2" # Try to enable if disabled, if not found then add new source EnableInternalPackageSource "$SourceName" if [ "$?" != "0" ]; then AddPackageSource "$SourceName" "$SourceEndPoint" fi } # Enable all darc-int package sources EnableMaestroInternalPackageSources() { # Check if disabledPackageSources section exists grep -i "" "$ConfigFile" > /dev/null if [ "$?" != "0" ]; then return # No disabled sources section fi # Find all darc-int disabled sources local DisabledDarcIntSources=() DisabledDarcIntSources+=$(grep -oh '"darc-int-[^"]*" value="true"' "$ConfigFile" | tr -d '"') for DisabledSourceName in ${DisabledDarcIntSources[@]} ; do if [[ $DisabledSourceName == darc-int* ]]; then EnableInternalPackageSource "$DisabledSourceName" fi done } # Ensure there is a ... section. grep -i "" $ConfigFile if [ "$?" != "0" ]; then Write-PipelineTelemetryError -Category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. NuGet config file must contain a packageSources section: $ConfigFile" ExitWithExitCode 1 fi PackageSources=() # Set feed suffix based on whether credentials are provided FeedSuffix="v3/index.json" if [ -n "$CredToken" ]; then FeedSuffix="v2" # Ensure there is a ... section. grep -i "" $ConfigFile if [ "$?" != "0" ]; then echo "Adding ... section." PackageSourcesNodeFooter="" PackageSourceCredentialsTemplate="${TB}${NL}${TB}" sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourcesNodeFooter${NL}$PackageSourceCredentialsTemplate|" $ConfigFile fi fi # Check for disabledPackageSources; we'll enable any darc-int ones we find there grep -i "" $ConfigFile > /dev/null if [ "$?" == "0" ]; then echo "Checking for any darc-int disabled package sources in the disabledPackageSources node" EnableMaestroInternalPackageSources fi DotNetVersions=('5' '6' '7' '8' '9' '10') for DotNetVersion in ${DotNetVersions[@]} ; do FeedPrefix="dotnet${DotNetVersion}"; grep -i " /dev/null if [ "$?" == "0" ]; then AddOrEnablePackageSource "$FeedPrefix-internal" "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$FeedPrefix-internal/nuget/$FeedSuffix" AddOrEnablePackageSource "$FeedPrefix-internal-transport" "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$FeedPrefix-internal-transport/nuget/$FeedSuffix" fi done # I want things split line by line PrevIFS=$IFS IFS=$'\n' PackageSources+="$IFS" PackageSources+=$(grep -oh '"darc-int-[^"]*"' $ConfigFile | tr -d '"') IFS=$PrevIFS if [ "$CredToken" ]; then for FeedName in ${PackageSources[@]} ; do # Check if there is no existing credential for this FeedName grep -i "<$FeedName>" $ConfigFile if [ "$?" != "0" ]; then echo " Inserting credential for feed: $FeedName" PackageSourceCredentialsNodeFooter="" NewCredential="${TB}${TB}<$FeedName>${NL}${TB}${NL}${TB}${TB}${NL}${TB}${TB}" sed -i.bak "s|$PackageSourceCredentialsNodeFooter|$NewCredential${NL}$PackageSourceCredentialsNodeFooter|" $ConfigFile fi done fi ================================================ FILE: eng/common/build.cmd ================================================ @echo off powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0build.ps1""" %*" exit /b %ErrorLevel% ================================================ FILE: eng/common/build.ps1 ================================================ [CmdletBinding(PositionalBinding=$false)] Param( [string][Alias('c')]$configuration = "Debug", [string]$platform = $null, [string] $projects, [string][Alias('v')]$verbosity = "minimal", [string] $msbuildEngine = $null, [bool] $warnAsError = $true, [string] $warnNotAsError = '', [bool] $nodeReuse = $true, [switch] $buildCheck = $false, [switch][Alias('r')]$restore, [switch] $deployDeps, [switch][Alias('b')]$build, [switch] $rebuild, [switch] $deploy, [switch][Alias('t')]$test, [switch] $integrationTest, [switch] $performanceTest, [switch] $sign, [switch] $pack, [switch] $publish, [switch] $clean, [switch][Alias('pb')]$productBuild, [switch]$fromVMR, [switch][Alias('bl')]$binaryLog, [switch][Alias('nobl')]$excludeCIBinarylog, [switch] $ci, [switch] $prepareMachine, [string] $runtimeSourceFeed = '', [string] $runtimeSourceFeedKey = '', [switch] $excludePrereleaseVS, [switch] $nativeToolsOnMachine, [switch] $help, [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties ) # Unset 'Platform' environment variable to avoid unwanted collision in InstallDotNetCore.targets file # some computer has this env var defined (e.g. Some HP) if($env:Platform) { $env:Platform="" } function Print-Usage() { Write-Host "Common settings:" Write-Host " -configuration Build configuration: 'Debug' or 'Release' (short: -c)" Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild" Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" Write-Host " -binaryLog Output binary log (short: -bl)" Write-Host " -help Print help and exit" Write-Host "" Write-Host "Actions:" Write-Host " -restore Restore dependencies (short: -r)" Write-Host " -build Build solution (short: -b)" Write-Host " -rebuild Rebuild solution" Write-Host " -deploy Deploy built VSIXes" Write-Host " -deployDeps Deploy dependencies (e.g. VSIXes for integration tests)" Write-Host " -test Run all unit tests in the solution (short: -t)" Write-Host " -integrationTest Run all integration tests in the solution" Write-Host " -performanceTest Run all performance tests in the solution" Write-Host " -pack Package build outputs into NuGet packages and Willow components" Write-Host " -sign Sign build outputs" Write-Host " -publish Publish artifacts (e.g. symbols)" Write-Host " -clean Clean the solution" Write-Host " -productBuild Build the solution in the way it will be built in the full .NET product (VMR) build (short: -pb)" Write-Host "" Write-Host "Advanced settings:" Write-Host " -projects Semi-colon delimited list of sln/proj's to build. Globbing is supported (*.sln)" Write-Host " -ci Set when running on CI server" Write-Host " -excludeCIBinarylog Don't output binary log (short: -nobl)" Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" Write-Host " -warnNotAsError Sets a semi-colon delimited list of warning codes that should not be treated as errors" Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio" Write-Host " -nativeToolsOnMachine Sets the native tools on machine environment variable (indicating that the script should use native tools on machine)" Write-Host " -nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" Write-Host " -buildCheck Sets /check msbuild parameter" Write-Host " -fromVMR Set when building from within the VMR" Write-Host "" Write-Host "Command line arguments not listed above are passed thru to msbuild." Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." } . $PSScriptRoot\tools.ps1 function InitializeCustomToolset { if (-not $restore) { return } $script = Join-Path $EngRoot 'restore-toolset.ps1' if (Test-Path $script) { . $script } } function Build { $toolsetBuildProj = InitializeToolset InitializeCustomToolset $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' } $platformArg = if ($platform) { "/p:Platform=$platform" } else { '' } $check = if ($buildCheck) { '/check' } else { '' } if ($projects) { # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. # Explicitly set the type as string[] because otherwise PowerShell would make this char[] if $properties is empty. [string[]] $msbuildArgs = $properties # Resolve relative project paths into full paths $projects = ($projects.Split(';').ForEach({Resolve-Path $_}) -join ';') $msbuildArgs += "/p:Projects=$projects" $properties = $msbuildArgs } MSBuild $toolsetBuildProj ` $bl ` $platformArg ` $check ` /p:Configuration=$configuration ` /p:RepoRoot=$RepoRoot ` /p:Restore=$restore ` /p:DeployDeps=$deployDeps ` /p:Build=$build ` /p:Rebuild=$rebuild ` /p:Deploy=$deploy ` /p:Test=$test ` /p:Pack=$pack ` /p:DotNetBuild=$productBuild ` /p:DotNetBuildFromVMR=$fromVMR ` /p:IntegrationTest=$integrationTest ` /p:PerformanceTest=$performanceTest ` /p:Sign=$sign ` /p:Publish=$publish ` /p:RestoreStaticGraphEnableBinaryLogger=$binaryLog ` @properties } try { if ($clean) { if (Test-Path $ArtifactsDir) { Remove-Item -Recurse -Force $ArtifactsDir Write-Host 'Artifacts directory deleted.' } exit 0 } if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { Print-Usage exit 0 } if ($ci) { if (-not $excludeCIBinarylog) { $binaryLog = $true } $nodeReuse = $false } if ($nativeToolsOnMachine) { $env:NativeToolsOnMachine = $true } if ($restore) { InitializeNativeTools } Build } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } ExitWithExitCode 0 ================================================ FILE: eng/common/build.sh ================================================ #!/usr/bin/env bash # Stop script if unbound variable found (use ${var:-} if intentional) set -u # Stop script if command returns non-zero exit code. # Prevents hidden errors caused by missing error code propagation. set -e usage() { echo "Common settings:" echo " --configuration Build configuration: 'Debug' or 'Release' (short: -c)" echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" echo " --binaryLog Create MSBuild binary log (short: -bl)" echo " --help Print help and exit (short: -h)" echo "" echo "Actions:" echo " --restore Restore dependencies (short: -r)" echo " --build Build solution (short: -b)" echo " --sourceBuild Source-build the solution (short: -sb)" echo " Will additionally trigger the following actions: --restore, --build, --pack" echo " If --configuration is not set explicitly, will also set it to 'Release'" echo " --productBuild Build the solution in the way it will be built in the full .NET product (VMR) build (short: -pb)" echo " Will additionally trigger the following actions: --restore, --build, --pack" echo " If --configuration is not set explicitly, will also set it to 'Release'" echo " --rebuild Rebuild solution" echo " --test Run all unit tests in the solution (short: -t)" echo " --integrationTest Run all integration tests in the solution" echo " --performanceTest Run all performance tests in the solution" echo " --pack Package build outputs into NuGet packages and Willow components" echo " --sign Sign build outputs" echo " --publish Publish artifacts (e.g. symbols)" echo " --clean Clean the solution" echo "" echo "Advanced settings:" echo " --projects Project or solution file(s) to build" echo " --ci Set when running on CI server" echo " --excludeCIBinarylog Don't output binary log (short: -nobl)" echo " --prepareMachine Prepare machine for CI run, clean up processes after build" echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" echo " --warnNotAsError Sets a semi-colon delimited list of warning codes that should not be treated as errors" echo " --buildCheck Sets /check msbuild parameter" echo " --fromVMR Set when building from within the VMR" echo "" echo "Command line arguments not listed above are passed thru to msbuild." echo "Arguments can also be passed in with a single hyphen." } source="${BASH_SOURCE[0]}" # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" restore=false build=false source_build=false product_build=false from_vmr=false rebuild=false test=false integration_test=false performance_test=false pack=false publish=false sign=false public=false ci=false clean=false warn_as_error=true warn_not_as_error='' node_reuse=true build_check=false binary_log=false exclude_ci_binary_log=false pipelines_log=false projects='' configuration='' prepare_machine=false verbosity='minimal' runtime_source_feed='' runtime_source_feed_key='' properties=() while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -help|-h) usage exit 0 ;; -clean) clean=true ;; -configuration|-c) configuration=$2 shift ;; -verbosity|-v) verbosity=$2 shift ;; -binarylog|-bl) binary_log=true ;; -excludecibinarylog|-nobl) exclude_ci_binary_log=true ;; -pipelineslog|-pl) pipelines_log=true ;; -restore|-r) restore=true ;; -build|-b) build=true ;; -rebuild) rebuild=true ;; -pack) pack=true ;; -sourcebuild|-source-build|-sb) build=true source_build=true product_build=true restore=true pack=true ;; -productbuild|-product-build|-pb) build=true product_build=true restore=true pack=true ;; -fromvmr|-from-vmr) from_vmr=true ;; -test|-t) test=true ;; -integrationtest) integration_test=true ;; -performancetest) performance_test=true ;; -sign) sign=true ;; -publish) publish=true ;; -preparemachine) prepare_machine=true ;; -projects) projects=$2 shift ;; -ci) ci=true ;; -warnaserror) warn_as_error=$2 shift ;; -warnnotaserror) warn_not_as_error=$2 shift ;; -nodereuse) node_reuse=$2 shift ;; -buildcheck) build_check=true ;; -runtimesourcefeed) runtime_source_feed=$2 shift ;; -runtimesourcefeedkey) runtime_source_feed_key=$2 shift ;; *) properties+=("$1") ;; esac shift done if [[ -z "$configuration" ]]; then if [[ "$source_build" = true ]]; then configuration="Release"; else configuration="Debug"; fi fi if [[ "$ci" == true ]]; then pipelines_log=true node_reuse=false if [[ "$exclude_ci_binary_log" == false ]]; then binary_log=true fi fi . "$scriptroot/tools.sh" function InitializeCustomToolset { local script="$eng_root/restore-toolset.sh" if [[ -a "$script" ]]; then . "$script" fi } function Build { InitializeToolset InitializeCustomToolset if [[ ! -z "$projects" ]]; then properties+=("/p:Projects=$projects") fi local bl="" if [[ "$binary_log" == true ]]; then bl="/bl:\"$log_dir/Build.binlog\"" fi local check="" if [[ "$build_check" == true ]]; then check="/check" fi MSBuild $_InitializeToolset \ $bl \ $check \ /p:Configuration=$configuration \ /p:RepoRoot="$repo_root" \ /p:Restore=$restore \ /p:Build=$build \ /p:DotNetBuild=$product_build \ /p:DotNetBuildSourceOnly=$source_build \ /p:DotNetBuildFromVMR=$from_vmr \ /p:Rebuild=$rebuild \ /p:Test=$test \ /p:Pack=$pack \ /p:IntegrationTest=$integration_test \ /p:PerformanceTest=$performance_test \ /p:Sign=$sign \ /p:Publish=$publish \ /p:RestoreStaticGraphEnableBinaryLogger=$binary_log \ ${properties[@]+"${properties[@]}"} ExitWithExitCode 0 } if [[ "$clean" == true ]]; then if [ -d "$artifacts_dir" ]; then rm -rf $artifacts_dir echo "Artifacts directory deleted." fi exit 0 fi if [[ "$restore" == true ]]; then InitializeNativeTools fi Build ================================================ FILE: eng/common/cibuild.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" # resolve $SOURCE until the file is no longer a symlink while [[ -h $source ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where # the symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ ================================================ FILE: eng/common/core-templates/job/job.yml ================================================ parameters: # Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job cancelTimeoutInMinutes: '' condition: '' container: '' continueOnError: false dependsOn: '' displayName: '' pool: '' steps: [] strategy: '' timeoutInMinutes: '' variables: [] workspace: '' templateContext: {} # Job base template specific parameters # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md # publishing defaults artifacts: '' enableMicrobuild: false enablePreviewMicrobuild: false microbuildPluginVersion: 'latest' enableMicrobuildForMacAndLinux: false microbuildUseESRP: true enablePublishBuildArtifacts: false enablePublishBuildAssets: false enablePublishTestResults: false enablePublishing: false enableBuildRetry: false mergeTestResults: false testRunTitle: '' testResultsFormat: '' name: '' preSteps: [] artifactPublishSteps: [] runAsPublic: false # 1es specific parameters is1ESPipeline: '' jobs: - job: ${{ parameters.name }} ${{ if ne(parameters.cancelTimeoutInMinutes, '') }}: cancelTimeoutInMinutes: ${{ parameters.cancelTimeoutInMinutes }} ${{ if ne(parameters.condition, '') }}: condition: ${{ parameters.condition }} ${{ if ne(parameters.container, '') }}: container: ${{ parameters.container }} ${{ if ne(parameters.continueOnError, '') }}: continueOnError: ${{ parameters.continueOnError }} ${{ if ne(parameters.dependsOn, '') }}: dependsOn: ${{ parameters.dependsOn }} ${{ if ne(parameters.displayName, '') }}: displayName: ${{ parameters.displayName }} ${{ if ne(parameters.pool, '') }}: pool: ${{ parameters.pool }} ${{ if ne(parameters.strategy, '') }}: strategy: ${{ parameters.strategy }} ${{ if ne(parameters.timeoutInMinutes, '') }}: timeoutInMinutes: ${{ parameters.timeoutInMinutes }} ${{ if ne(parameters.templateContext, '') }}: templateContext: ${{ parameters.templateContext }} variables: - name: AllowPtrToDetectTestRunRetryFiles value: true - ${{ if ne(parameters.enableTelemetry, 'false') }}: - name: DOTNET_CLI_TELEMETRY_PROFILE value: '$(Build.Repository.Uri)' # Retry signature validation up to three times, waiting 2 seconds between attempts. # See https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3028#retry-untrusted-root-failures - name: NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY value: 3,2000 - ${{ each variable in parameters.variables }}: # handle name-value variable syntax # example: # - name: [key] # value: [value] - ${{ if ne(variable.name, '') }}: - name: ${{ variable.name }} value: ${{ variable.value }} # handle variable groups - ${{ if ne(variable.group, '') }}: - group: ${{ variable.group }} # handle template variable syntax # example: # - template: path/to/template.yml # parameters: # [key]: [value] - ${{ if ne(variable.template, '') }}: - template: ${{ variable.template }} ${{ if ne(variable.parameters, '') }}: parameters: ${{ variable.parameters }} # handle key-value variable syntax. # example: # - [key]: [value] - ${{ if and(eq(variable.name, ''), eq(variable.group, ''), eq(variable.template, '')) }}: - ${{ each pair in variable }}: - name: ${{ pair.key }} value: ${{ pair.value }} # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds - ${{ if and(eq(parameters.enableTelemetry, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - group: DotNet-HelixApi-Access ${{ if ne(parameters.workspace, '') }}: workspace: ${{ parameters.workspace }} steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if ne(parameters.preSteps, '') }}: - ${{ each preStep in parameters.preSteps }}: - ${{ preStep }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - template: /eng/common/core-templates/steps/install-microbuild.yml parameters: enableMicrobuild: ${{ parameters.enableMicrobuild }} enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} microbuildPluginVersion: ${{ parameters.microbuildPluginVersion }} enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} microbuildUseESRP: ${{ parameters.microbuildUseESRP }} continueOnError: ${{ parameters.continueOnError }} - ${{ if and(eq(parameters.runAsPublic, 'false'), eq(variables['System.TeamProject'], 'internal')) }}: - task: NuGetAuthenticate@1 - ${{ if and(ne(parameters.artifacts.download, 'false'), ne(parameters.artifacts.download, '')) }}: - task: DownloadPipelineArtifact@2 inputs: buildType: current artifactName: ${{ coalesce(parameters.artifacts.download.name, 'Artifacts_$(Agent.OS)_$(_BuildConfig)') }} targetPath: ${{ coalesce(parameters.artifacts.download.path, 'artifacts') }} itemPattern: ${{ coalesce(parameters.artifacts.download.pattern, '**') }} - ${{ each step in parameters.steps }}: - ${{ step }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - template: /eng/common/core-templates/steps/cleanup-microbuild.yml parameters: enableMicrobuild: ${{ parameters.enableMicrobuild }} enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} microbuildPluginVersion: ${{ parameters.microbuildPluginVersion }} enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} continueOnError: ${{ parameters.continueOnError }} # Publish test results - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'xunit')) }}: - task: PublishTestResults@2 displayName: Publish XUnit Test Results inputs: testResultsFormat: 'xUnit' testResultsFiles: '*.xml' searchFolder: '$(System.DefaultWorkingDirectory)/artifacts/TestResults/$(_BuildConfig)' testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-xunit mergeTestResults: ${{ parameters.mergeTestResults }} continueOnError: true condition: always() - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'vstest')) }}: - task: PublishTestResults@2 displayName: Publish TRX Test Results inputs: testResultsFormat: 'VSTest' testResultsFiles: '*.trx' searchFolder: '$(System.DefaultWorkingDirectory)/artifacts/TestResults/$(_BuildConfig)' testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-trx mergeTestResults: ${{ parameters.mergeTestResults }} continueOnError: true condition: always() # gather artifacts - ${{ if ne(parameters.artifacts.publish, '') }}: - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: - task: CopyFiles@2 displayName: Gather binaries for publish to artifacts inputs: SourceFolder: 'artifacts/bin' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/bin' - task: CopyFiles@2 displayName: Gather packages for publish to artifacts inputs: SourceFolder: 'artifacts/packages' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/packages' - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: - task: CopyFiles@2 displayName: Gather logs for publish to artifacts inputs: SourceFolder: 'artifacts/log' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/log' continueOnError: true condition: always() - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: - task: CopyFiles@2 displayName: Gather logs for publish to artifacts inputs: SourceFolder: 'artifacts/log/$(_BuildConfig)' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' continueOnError: true condition: always() - ${{ if eq(parameters.enableBuildRetry, 'true') }}: - task: CopyFiles@2 displayName: Gather buildconfiguration for build retry inputs: SourceFolder: '$(System.DefaultWorkingDirectory)/eng/common/BuildConfiguration' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/eng/common/BuildConfiguration' continueOnError: true condition: always() - ${{ each step in parameters.artifactPublishSteps }}: - ${{ step }} ================================================ FILE: eng/common/core-templates/job/onelocbuild.yml ================================================ parameters: # Optional: dependencies of the job dependsOn: '' # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool pool: '' CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex GithubPat: $(BotAccount-dotnet-bot-repo-PAT) SourcesDirectory: $(System.DefaultWorkingDirectory) CreatePr: true AutoCompletePr: false ReusePr: true UseLfLineEndings: true UseCheckedInLocProjectJson: false SkipLocProjectJsonGeneration: false LanguageSet: VS_Main_Languages LclSource: lclFilesInRepo LclPackageId: '' RepoType: gitHub GitHubOrg: dotnet MirrorRepo: '' MirrorBranch: main xLocCustomPowerShellScript: '' condition: '' JobNameSuffix: '' is1ESPipeline: '' jobs: - job: OneLocBuild${{ parameters.JobNameSuffix }} dependsOn: ${{ parameters.dependsOn }} displayName: OneLocBuild${{ parameters.JobNameSuffix }} variables: - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat - name: _GenerateLocProjectArguments value: -SourcesDirectory ${{ parameters.SourcesDirectory }} -LanguageSet "${{ parameters.LanguageSet }}" -CreateNeutralXlfs - ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: - name: _GenerateLocProjectArguments value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} ${{ if ne(parameters.pool, '') }}: pool: ${{ parameters.pool }} ${{ if eq(parameters.pool, '') }}: pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO image: 1ESPT-Windows2025 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: name: $(DncEngInternalBuildPool) image: windows.vs2026.amd64 os: windows steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if ne(parameters.SkipLocProjectJsonGeneration, 'true') }}: - task: Powershell@2 inputs: filePath: $(System.DefaultWorkingDirectory)/eng/common/generate-locproject.ps1 arguments: $(_GenerateLocProjectArguments) displayName: Generate LocProject.json condition: ${{ parameters.condition }} - task: OneLocBuild@2 displayName: OneLocBuild env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) inputs: locProj: eng/Localize/LocProject.json outDir: $(Build.ArtifactStagingDirectory) lclSource: ${{ parameters.LclSource }} lclPackageId: ${{ parameters.LclPackageId }} isCreatePrSelected: ${{ parameters.CreatePr }} isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} ${{ if eq(parameters.CreatePr, true) }}: isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }} isShouldReusePrSelected: ${{ parameters.ReusePr }} packageSourceAuth: patAuth patVariable: ${{ parameters.CeapexPat }} ${{ if eq(parameters.RepoType, 'gitHub') }}: repoType: ${{ parameters.RepoType }} gitHubPatVariable: "${{ parameters.GithubPat }}" ${{ if ne(parameters.MirrorRepo, '') }}: isMirrorRepoSelected: true gitHubOrganization: ${{ parameters.GitHubOrg }} mirrorRepo: ${{ parameters.MirrorRepo }} mirrorBranch: ${{ parameters.MirrorBranch }} ${{ if ne(parameters.xLocCustomPowerShellScript, '') }}: xLocCustomPowerShellScript: ${{ parameters.xLocCustomPowerShellScript }} condition: ${{ parameters.condition }} # Copy the locProject.json to the root of the Loc directory, then publish a pipeline artifact - task: CopyFiles@2 displayName: Copy LocProject.json inputs: SourceFolder: '$(System.DefaultWorkingDirectory)/eng/Localize/' Contents: 'LocProject.json' TargetFolder: '$(Build.ArtifactStagingDirectory)/loc' condition: ${{ parameters.condition }} - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: targetPath: '$(Build.ArtifactStagingDirectory)/loc' artifactName: 'Loc' displayName: 'Publish Localization Files' condition: ${{ parameters.condition }} ================================================ FILE: eng/common/core-templates/job/publish-build-assets.yml ================================================ parameters: configuration: 'Debug' # Optional: condition for the job to run condition: '' # Optional: 'true' if future jobs should run even if this job fails continueOnError: false # Optional: dependencies of the job dependsOn: '' # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool pool: {} # Optional: should run as a public build even in the internal project # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. runAsPublic: false # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing publishAssetsImmediately: false artifactsPublishingAdditionalParameters: '' signingValidationAdditionalParameters: '' is1ESPipeline: '' # Optional: 🌤️ or not the build has assets it wants to publish to BAR isAssetlessBuild: false # Optional, publishing version publishingVersion: 3 # Optional: A minimatch pattern for the asset manifests to publish to BAR assetManifestsPattern: '*/manifests/**/*.xml' repositoryAlias: self officialBuildId: '' jobs: - job: Asset_Registry_Publish dependsOn: ${{ parameters.dependsOn }} timeoutInMinutes: 150 ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: displayName: Publish Assets ${{ else }}: displayName: Publish to Build Asset Registry variables: - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - group: Publish-Build-Assets - group: AzureDevOps-Artifact-Feeds-Pats - name: runCodesignValidationInjection value: false # unconditional - needed for logs publishing (redactor tool version) - template: /eng/common/core-templates/post-build/common-variables.yml - name: OfficialBuildId ${{ if ne(parameters.officialBuildId, '') }}: value: ${{ parameters.officialBuildId }} ${{ else }}: value: $(Build.BuildNumber) pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO image: 1ESPT-Windows2025 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: name: NetCore1ESPool-Publishing-Internal image: windows.vs2026.amd64 os: windows steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - checkout: ${{ parameters.repositoryAlias }} fetchDepth: 3 clean: true - ${{ if eq(parameters.isAssetlessBuild, 'false') }}: - ${{ if eq(parameters.publishingVersion, 3) }}: - task: DownloadPipelineArtifact@2 displayName: Download Asset Manifests inputs: artifactName: AssetManifests targetPath: '$(Build.StagingDirectory)/AssetManifests' condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - ${{ if eq(parameters.publishingVersion, 4) }}: - task: DownloadPipelineArtifact@2 displayName: Download V4 asset manifests inputs: itemPattern: '*/manifests/**/*.xml' targetPath: '$(Build.StagingDirectory)/AllAssetManifests' condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - task: CopyFiles@2 displayName: Copy V4 asset manifests to AssetManifests inputs: SourceFolder: '$(Build.StagingDirectory)/AllAssetManifests' Contents: ${{ parameters.assetManifestsPattern }} TargetFolder: '$(Build.StagingDirectory)/AssetManifests' flattenFolders: true condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - task: NuGetAuthenticate@1 # Populate internal runtime variables. - template: /eng/common/templates/steps/enable-internal-sources.yml ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: parameters: legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw) - template: /eng/common/templates/steps/enable-internal-runtimes.yml - task: AzureCLI@2 displayName: Publish Build Assets inputs: azureSubscription: "Darc: Maestro Production" scriptType: ps scriptLocation: scriptPath scriptPath: $(System.DefaultWorkingDirectory)/eng/common/sdk-task.ps1 arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet /p:ManifestsPath='$(Build.StagingDirectory)/AssetManifests' /p:IsAssetlessBuild=${{ parameters.isAssetlessBuild }} /p:MaestroApiEndpoint=https://maestro.dot.net /p:OfficialBuildId=$(OfficialBuildId) -runtimeSourceFeed https://ci.dot.net/internal -runtimeSourceFeedKey '$(dotnetbuilds-internal-container-read-token-base64)' condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - task: powershell@2 displayName: Create ReleaseConfigs Artifact inputs: targetType: inline script: | New-Item -Path "$(Build.StagingDirectory)/ReleaseConfigs" -ItemType Directory -Force $filePath = "$(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt" Add-Content -Path $filePath -Value $(BARBuildId) Add-Content -Path $filePath -Value "$(DefaultChannels)" Add-Content -Path $filePath -Value $(IsStableBuild) $symbolExclusionfile = "$(System.DefaultWorkingDirectory)/eng/SymbolPublishingExclusionsFile.txt" if (Test-Path -Path $symbolExclusionfile) { Write-Host "SymbolExclusionFile exists" Copy-Item -Path $symbolExclusionfile -Destination "$(Build.StagingDirectory)/ReleaseConfigs" } - ${{ if eq(parameters.publishingVersion, 4) }}: - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: targetPath: '$(Build.ArtifactStagingDirectory)/MergedManifest.xml' artifactName: AssetManifests displayName: 'Publish Merged Manifest' retryCountOnTaskFailure: 10 # for any files being locked isProduction: false # just metadata for publishing - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish ReleaseConfigs Artifact targetPath: '$(Build.StagingDirectory)/ReleaseConfigs' artifactName: ReleaseConfigs retryCountOnTaskFailure: 10 # for any files being locked isProduction: false # just metadata for publishing - ${{ if or(eq(parameters.publishAssetsImmediately, 'true'), eq(parameters.isAssetlessBuild, 'true')) }}: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} # Darc is targeting 8.0, so make sure it's installed - task: UseDotNet@2 inputs: version: 8.0.x - task: AzureCLI@2 displayName: Publish Using Darc inputs: azureSubscription: "Darc: Maestro Production" scriptType: ps scriptLocation: scriptPath scriptPath: $(System.DefaultWorkingDirectory)/eng/common/post-build/publish-using-darc.ps1 arguments: > -BuildId $(BARBuildId) -PublishingInfraVersion 3 -AzdoToken '$(System.AccessToken)' -WaitPublishingFinish true -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' -SkipAssetsPublishing '${{ parameters.isAssetlessBuild }}' -runtimeSourceFeed https://ci.dot.net/internal -runtimeSourceFeedKey '$(dotnetbuilds-internal-container-read-token-base64)' - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: - template: /eng/common/core-templates/steps/publish-logs.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} StageLabel: 'BuildAssetRegistry' JobLabel: 'Publish_Artifacts_Logs' ================================================ FILE: eng/common/core-templates/job/renovate.yml ================================================ # -------------------------------------------------------------------------------------- # Renovate Bot Job Template # -------------------------------------------------------------------------------------- # This Azure DevOps pipeline job template runs Renovate (https://docs.renovatebot.com/) # to automatically update dependencies in a GitHub repository. # # Renovate scans the repository for dependency files and creates pull requests to update # outdated dependencies based on the configuration specified in the renovateConfigPath # parameter. # # Usage: # For each product repo wanting to make use of Renovate, this template is called from # an internal Azure DevOps pipeline, typically with a schedule trigger, to check for # and propose dependency updates. # # For more info, see https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md # -------------------------------------------------------------------------------------- parameters: # Path to the Renovate configuration file within the repository. - name: renovateConfigPath type: string default: 'eng/renovate.json' # GitHub repository to run Renovate against, in the format 'owner/repo'. # This could technically be any repo but convention is to target the same # repo that contains the calling pipeline. The Renovate config file would # be co-located with the pipeline's repo and, in most cases, the config # file is specific to the repo being targeted. - name: gitHubRepo type: string # List of base branches to target for Renovate PRs. # NOTE: The Renovate configuration file is always read from the branch where the # pipeline is run, NOT from the target branches specified here. If you need different # configurations for different branches, run the pipeline from each branch separately. - name: baseBranches type: object default: - main # When true, Renovate will run in dry run mode, which previews changes without creating PRs. # See the 'Run Renovate' step log output for details of what would have been changed. - name: dryRun type: boolean default: false # By default, Renovate will not recreate a PR for a given dependency/version pair that was # previously closed. This allows opting in to always recreating PRs even if they were # previously closed. - name: forceRecreatePR type: boolean default: false # Name of the arcade repository resource in the pipeline. # This allows repos which haven't been onboarded to Arcade to still use this # template by checking out the repo as a resource with a custom name and pointing # this parameter to it. - name: arcadeRepoResource type: string default: self # Directory name for the self repo under $(Build.SourcesDirectory) in multi-checkout. # In multi-checkout (when arcadeRepoResource != 'self'), Azure DevOps checks out the # self repo to $(Build.SourcesDirectory)/. Set this to match the auto-generated # directory name. Using the auto-generated name is necessary rather than explicitly # defining a checkout path because container jobs expect repos to live under the agent's # workspace ($(Pipeline.Workspace)). On some self-hosted setups the host path # (e.g., /mnt/vss/_work) differs from the container path (e.g., /__w), and a custom checkout # path can fail validation. Using the default checkout location keeps the paths consistent # and avoids this issue. - name: selfRepoName type: string default: '' - name: arcadeRepoName type: string default: '' # Pool configuration for the job. - name: pool type: object default: name: NetCore1ESPool-Internal image: build.azurelinux.3.amd64 os: linux jobs: - job: Renovate displayName: Run Renovate container: RenovateContainer variables: - group: dotnet-renovate-bot # The Renovate version is automatically updated by https://github.com/dotnet/arcade/blob/main/azure-pipelines-renovate.yml. # Changing the variable name here would require updating the name in https://github.com/dotnet/arcade/blob/main/eng/renovate.json as well. - name: renovateVersion value: '42' readonly: true - name: renovateLogFilePath value: '$(Build.ArtifactStagingDirectory)/renovate.json' readonly: true - name: dryRunArg readonly: true ${{ if eq(parameters.dryRun, true) }}: value: 'full' ${{ else }}: value: '' - name: recreateWhenArg readonly: true ${{ if eq(parameters.forceRecreatePR, true) }}: value: 'always' ${{ else }}: value: '' # In multi-checkout (without custom paths), Azure DevOps places each repo under # $(Build.SourcesDirectory)/. selfRepoName must be provided in that case. - name: selfRepoPath readonly: true ${{ if eq(parameters.arcadeRepoResource, 'self') }}: value: '$(Build.SourcesDirectory)' ${{ else }}: value: '$(Build.SourcesDirectory)/${{ parameters.selfRepoName }}' - name: arcadeRepoPath readonly: true ${{ if eq(parameters.arcadeRepoResource, 'self') }}: value: '$(Build.SourcesDirectory)' ${{ else }}: value: '$(Build.SourcesDirectory)/${{ parameters.arcadeRepoName }}' pool: ${{ parameters.pool }} templateContext: outputParentDirectory: $(Build.ArtifactStagingDirectory) outputs: - output: pipelineArtifact displayName: Publish Renovate Log condition: succeededOrFailed() targetPath: $(Build.ArtifactStagingDirectory) artifactName: $(Agent.JobName)_Logs_Attempt$(System.JobAttempt) isProduction: false # logs are non-production artifacts steps: - checkout: self fetchDepth: 1 - ${{ if ne(parameters.arcadeRepoResource, 'self') }}: - checkout: ${{ parameters.arcadeRepoResource }} fetchDepth: 1 - script: | renovate-config-validator $(selfRepoPath)/${{parameters.renovateConfigPath}} 2>&1 | tee /tmp/renovate-config-validator.out validatorExit=${PIPESTATUS[0]} if grep -q '^ WARN:' /tmp/renovate-config-validator.out; then echo "##vso[task.logissue type=warning]Renovate config validator produced warnings." echo "##vso[task.complete result=SucceededWithIssues]" fi exit $validatorExit displayName: Validate Renovate config env: LOG_LEVEL: info LOG_FILE_LEVEL: debug LOG_FILE: $(Build.ArtifactStagingDirectory)/renovate-config-validator.json - script: | . $(arcadeRepoPath)/eng/common/renovate.env renovate 2>&1 | tee /tmp/renovate.out renovateExit=${PIPESTATUS[0]} if grep -q '^ WARN:' /tmp/renovate.out; then echo "##vso[task.logissue type=warning]Renovate produced warnings." echo "##vso[task.complete result=SucceededWithIssues]" fi exit $renovateExit displayName: Run Renovate env: RENOVATE_FORK_TOKEN: $(BotAccount-dotnet-renovate-bot-PAT) RENOVATE_TOKEN: $(BotAccount-dotnet-renovate-bot-PAT) RENOVATE_REPOSITORIES: ${{parameters.gitHubRepo}} RENOVATE_BASE_BRANCHES: ${{ convertToJson(parameters.baseBranches) }} RENOVATE_DRY_RUN: $(dryRunArg) RENOVATE_RECREATE_WHEN: $(recreateWhenArg) LOG_LEVEL: info LOG_FILE_LEVEL: debug LOG_FILE: $(renovateLogFilePath) RENOVATE_CONFIG_FILE: $(selfRepoPath)/${{parameters.renovateConfigPath}} - script: | echo "PRs created by Renovate:" if [ -s "$(renovateLogFilePath)" ]; then if ! jq -r 'select(.msg == "PR created" and .pr != null) | "https://github.com/\(.repository)/pull/\(.pr)"' "$(renovateLogFilePath)" | sort -u; then echo "##vso[task.logissue type=warning]Failed to parse Renovate log file with jq." echo "##vso[task.complete result=SucceededWithIssues]" fi else echo "##vso[task.logissue type=warning]No Renovate log file found or file is empty." echo "##vso[task.complete result=SucceededWithIssues]" fi displayName: List created PRs condition: and(succeededOrFailed(), eq('${{ parameters.dryRun }}', false)) ================================================ FILE: eng/common/core-templates/job/source-build.yml ================================================ parameters: # This template adds arcade-powered source-build to CI. The template produces a server job with a # default ID 'Source_Build_Complete' to put in a dependency list if necessary. # Specifies the prefix for source-build jobs added to pipeline. Use this if disambiguation needed. jobNamePrefix: 'Source_Build' # Defines the platform on which to run the job. By default, a linux-x64 machine, suitable for # managed-only repositories. This is an object with these properties: # # name: '' # The name of the job. This is included in the job ID. # targetRID: '' # The name of the target RID to use, instead of the one auto-detected by Arcade. # portableBuild: false # Enables non-portable mode. This means a more specific RID (e.g. fedora.32-x64 rather than # linux-x64), and compiling against distro-provided packages rather than portable ones. The # default is portable mode. # skipPublishValidation: false # Disables publishing validation. By default, a check is performed to ensure no packages are # published by source-build. # container: '' # A container to use. Runs in docker. # pool: {} # A pool to use. Runs directly on an agent. # buildScript: '' # Specifies the build script to invoke to perform the build in the repo. The default # './build.sh' should work for typical Arcade repositories, but this is customizable for # difficult situations. # buildArguments: '' # Specifies additional build arguments to pass to the build script. # jobProperties: {} # A list of job properties to inject at the top level, for potential extensibility beyond # container and pool. platform: {} is1ESPipeline: '' # If set to true and running on a non-public project, # Internal nuget and blob storage locations will be enabled. # This is not enabled by default because many repositories do not need internal sources # and do not need to have the required service connections approved in the pipeline. enableInternalSources: false jobs: - job: ${{ parameters.jobNamePrefix }}_${{ parameters.platform.name }} displayName: Source-Build (${{ parameters.platform.name }}) ${{ each property in parameters.platform.jobProperties }}: ${{ property.key }}: ${{ property.value }} ${{ if ne(parameters.platform.container, '') }}: container: ${{ parameters.platform.container }} ${{ if eq(parameters.platform.pool, '') }}: # The default VM host AzDO pool. This should be capable of running Docker containers: almost all # source-build builds run in Docker, including the default managed platform. # /eng/common/core-templates/variables/pool-providers.yml can't be used here (some customers declare variables already), so duplicate its logic ${{ if eq(parameters.is1ESPipeline, 'true') }}: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] demands: ImageOverride -equals build.azurelinux.3.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] image: build.azurelinux.3.amd64 os: linux ${{ else }}: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] demands: ImageOverride -equals build.azurelinux.3.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] demands: ImageOverride -equals build.azurelinux.3.amd64 ${{ if ne(parameters.platform.pool, '') }}: pool: ${{ parameters.platform.pool }} workspace: clean: all steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if eq(parameters.enableInternalSources, true) }}: - template: /eng/common/core-templates/steps/enable-internal-sources.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} - template: /eng/common/core-templates/steps/enable-internal-runtimes.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} - template: /eng/common/core-templates/steps/source-build.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} platform: ${{ parameters.platform }} ================================================ FILE: eng/common/core-templates/job/source-index-stage1.yml ================================================ parameters: runAsPublic: false sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" preSteps: [] binlogPath: artifacts/log/Debug/Build.binlog condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') dependsOn: '' pool: '' is1ESPipeline: '' jobs: - job: SourceIndexStage1 dependsOn: ${{ parameters.dependsOn }} condition: ${{ parameters.condition }} variables: - name: BinlogPath value: ${{ parameters.binlogPath }} - name: skipComponentGovernanceDetection value: true - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} ${{ if ne(parameters.pool, '') }}: pool: ${{ parameters.pool }} ${{ if eq(parameters.pool, '') }}: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $(DncEngPublicBuildPool) image: windows.vs2026.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $(DncEngInternalBuildPool) image: windows.vs2026.amd64 steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ each preStep in parameters.preSteps }}: - ${{ preStep }} - script: ${{ parameters.sourceIndexBuildCommand }} displayName: Build Repository - template: /eng/common/core-templates/steps/source-index-stage1-publish.yml parameters: binLogPath: ${{ parameters.binLogPath }} ================================================ FILE: eng/common/core-templates/jobs/jobs.yml ================================================ parameters: # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md continueOnError: false # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false # Optional: Enable running the source-build jobs to build repo from source enableSourceBuild: false # Optional: Parameters for source-build template. # See /eng/common/core-templates/jobs/source-build.yml for options sourceBuildParameters: [] graphFileGeneration: # Optional: Enable generating the graph files at the end of the build enabled: false # Optional: Include toolset dependencies in the generated graph files includeToolset: false # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job jobs: [] # Optional: Override automatically derived dependsOn value for "publish build assets" job publishBuildAssetsDependsOn: '' # Optional: Publish the assets as soon as the publish to BAR stage is complete, rather doing so in a separate stage. publishAssetsImmediately: false # Optional: 🌤️ or not the build has assets it wants to publish to BAR isAssetlessBuild: false # Optional: If using publishAssetsImmediately and additional parameters are needed, can be used to send along additional parameters (normally sent to post-build.yml) artifactsPublishingAdditionalParameters: '' signingValidationAdditionalParameters: '' # Optional: should run as a public build even in the internal project # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. runAsPublic: false enableSourceIndex: false sourceIndexParams: {} artifacts: {} is1ESPipeline: '' # Publishing version w/default. publishingVersion: 3 repositoryAlias: self officialBuildId: '' # Internal resources (telemetry, microbuild) can only be accessed from non-public projects, # and some (Microbuild) should only be applied to non-PR cases for internal builds. jobs: - ${{ each job in parameters.jobs }}: - ${{ if eq(parameters.is1ESPipeline, 'true') }}: - template: /eng/common/templates-official/job/job.yml parameters: # pass along parameters ${{ each parameter in parameters }}: ${{ if ne(parameter.key, 'jobs') }}: ${{ parameter.key }}: ${{ parameter.value }} # pass along job properties ${{ each property in job }}: ${{ if ne(property.key, 'job') }}: ${{ property.key }}: ${{ property.value }} name: ${{ job.job }} - ${{ else }}: - template: /eng/common/templates/job/job.yml parameters: # pass along parameters ${{ each parameter in parameters }}: ${{ if ne(parameter.key, 'jobs') }}: ${{ parameter.key }}: ${{ parameter.value }} # pass along job properties ${{ each property in job }}: ${{ if ne(property.key, 'job') }}: ${{ property.key }}: ${{ property.value }} name: ${{ job.job }} - ${{ if eq(parameters.enableSourceBuild, true) }}: - template: /eng/common/core-templates/jobs/source-build.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} ${{ each parameter in parameters.sourceBuildParameters }}: ${{ parameter.key }}: ${{ parameter.value }} - ${{ if eq(parameters.enableSourceIndex, 'true') }}: - template: ../job/source-index-stage1.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} runAsPublic: ${{ parameters.runAsPublic }} ${{ each parameter in parameters.sourceIndexParams }}: ${{ parameter.key }}: ${{ parameter.value }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, ''), eq(parameters.isAssetlessBuild, true)) }}: - template: ../job/publish-build-assets.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} continueOnError: ${{ parameters.continueOnError }} publishingVersion: ${{ parameters.publishingVersion }} dependsOn: - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: - ${{ each job in parameters.publishBuildAssetsDependsOn }}: - ${{ job.job }} - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: - ${{ each job in parameters.jobs }}: - ${{ job.job }} runAsPublic: ${{ parameters.runAsPublic }} publishAssetsImmediately: ${{ or(parameters.publishAssetsImmediately, parameters.isAssetlessBuild) }} isAssetlessBuild: ${{ parameters.isAssetlessBuild }} enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} signingValidationAdditionalParameters: ${{ parameters.signingValidationAdditionalParameters }} repositoryAlias: ${{ parameters.repositoryAlias }} officialBuildId: ${{ parameters.officialBuildId }} ================================================ FILE: eng/common/core-templates/jobs/source-build.yml ================================================ parameters: # This template adds arcade-powered source-build to CI. A job is created for each platform, as # well as an optional server job that completes when all platform jobs complete. # See /eng/common/core-templates/job/source-build.yml jobNamePrefix: 'Source_Build' # This is the default platform provided by Arcade, intended for use by a managed-only repo. defaultManagedPlatform: name: 'Managed' container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream-10-amd64' # Defines the platforms on which to run build jobs. One job is created for each platform, and the # object in this array is sent to the job template as 'platform'. If no platforms are specified, # one job runs on 'defaultManagedPlatform'. platforms: [] is1ESPipeline: '' # If set to true and running on a non-public project, # Internal nuget and blob storage locations will be enabled. # This is not enabled by default because many repositories do not need internal sources # and do not need to have the required service connections approved in the pipeline. enableInternalSources: false jobs: - ${{ each platform in parameters.platforms }}: - template: /eng/common/core-templates/job/source-build.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} jobNamePrefix: ${{ parameters.jobNamePrefix }} platform: ${{ platform }} enableInternalSources: ${{ parameters.enableInternalSources }} - ${{ if eq(length(parameters.platforms), 0) }}: - template: /eng/common/core-templates/job/source-build.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} jobNamePrefix: ${{ parameters.jobNamePrefix }} platform: ${{ parameters.defaultManagedPlatform }} enableInternalSources: ${{ parameters.enableInternalSources }} ================================================ FILE: eng/common/core-templates/post-build/common-variables.yml ================================================ variables: - group: Publish-Build-Assets # Whether the build is internal or not - name: IsInternalBuild value: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }} # Default Maestro++ API Endpoint and API Version - name: MaestroApiEndPoint value: "https://maestro.dot.net" - name: MaestroApiVersion value: "2020-02-20" - name: SymbolToolVersion value: 1.0.1 - name: BinlogToolVersion value: 1.0.11 - name: runCodesignValidationInjection value: false ================================================ FILE: eng/common/core-templates/post-build/post-build.yml ================================================ parameters: # Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. # Publishing V1 is no longer supported # Publishing V2 is no longer supported # Publishing V3 is the default - name: publishingInfraVersion displayName: Which version of publishing should be used to promote the build definition? type: number default: 3 values: - 3 - 4 - name: BARBuildId displayName: BAR Build Id type: number default: 0 - name: PromoteToChannelIds displayName: Channel to promote BARBuildId to type: string default: '' - name: enableSourceLinkValidation displayName: Enable SourceLink validation type: boolean default: false - name: enableSigningValidation displayName: Enable signing validation type: boolean default: true - name: enableSymbolValidation displayName: Enable symbol validation type: boolean default: false - name: enableNugetValidation displayName: Enable NuGet validation type: boolean default: true - name: publishInstallersAndChecksums displayName: Publish installers and checksums type: boolean default: true - name: requireDefaultChannels displayName: Fail the build if there are no default channel(s) registrations for the current build type: boolean default: false - name: isAssetlessBuild type: boolean displayName: Is Assetless Build default: false # These parameters let the user customize the call to sdk-task.ps1 for publishing # symbols & general artifacts as well as for signing validation - name: symbolPublishingAdditionalParameters displayName: Symbol publishing additional parameters type: string default: '' - name: artifactsPublishingAdditionalParameters displayName: Artifact publishing additional parameters type: string default: '' - name: signingValidationAdditionalParameters displayName: Signing validation additional parameters type: string default: '' # Which stages should finish execution before post-build stages start - name: validateDependsOn type: object default: - build - name: publishDependsOn type: object default: - Validate # Optional: Call asset publishing rather than running in a separate stage - name: publishAssetsImmediately type: boolean default: false - name: is1ESPipeline type: boolean default: false stages: - ${{ if or(eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true')) }}: - stage: Validate dependsOn: ${{ parameters.validateDependsOn }} displayName: Validate Build Assets variables: - template: /eng/common/core-templates/post-build/common-variables.yml - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} jobs: - job: displayName: NuGet Validation condition: and(succeededOrFailed(), eq( ${{ parameters.enableNugetValidation }}, 'true')) pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO image: 1ESPT-Windows2025 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) image: windows.vs2026.amd64 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2026.amd64 steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - ${{ if ne(parameters.publishingInfraVersion, 4) }}: - task: DownloadBuildArtifacts@0 displayName: Download Package Artifacts inputs: buildType: specific buildVersionToDownload: specific project: $(AzDOProjectName) pipeline: $(AzDOPipelineId) buildId: $(AzDOBuildId) artifactName: PackageArtifacts checkDownloadedFiles: true - ${{ if eq(parameters.publishingInfraVersion, 4) }}: - task: DownloadPipelineArtifact@2 displayName: Download Pipeline Artifacts (V4) inputs: itemPattern: '*/packages/**/*.nupkg' targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - task: CopyFiles@2 displayName: Flatten packages to PackageArtifacts inputs: SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' Contents: '**/*.nupkg' TargetFolder: '$(Build.ArtifactStagingDirectory)/PackageArtifacts' flattenFolders: true - task: PowerShell@2 displayName: Validate inputs: filePath: $(System.DefaultWorkingDirectory)/eng/common/post-build/nuget-validation.ps1 arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ - job: displayName: Signing Validation condition: and( eq( ${{ parameters.enableSigningValidation }}, 'true'), ne( variables['PostBuildSign'], 'true')) pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO image: 1ESPT-Windows2025 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) image: windows.vs2026.amd64 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) demands: ImageOverride -equals windows.vs2026.amd64 steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - ${{ if ne(parameters.publishingInfraVersion, 4) }}: - task: DownloadBuildArtifacts@0 displayName: Download Package Artifacts inputs: buildType: specific buildVersionToDownload: specific project: $(AzDOProjectName) pipeline: $(AzDOPipelineId) buildId: $(AzDOBuildId) artifactName: PackageArtifacts checkDownloadedFiles: true - ${{ if eq(parameters.publishingInfraVersion, 4) }}: - task: DownloadPipelineArtifact@2 displayName: Download Pipeline Artifacts (V4) inputs: itemPattern: '*/packages/**/*.nupkg' targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - task: CopyFiles@2 displayName: Flatten packages to PackageArtifacts inputs: SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' Contents: '**/*.nupkg' TargetFolder: '$(Build.ArtifactStagingDirectory)/PackageArtifacts' flattenFolders: true # This is necessary whenever we want to publish/restore to an AzDO private feed # Since sdk-task.ps1 tries to restore packages we need to do this authentication here # otherwise it'll complain about accessing a private feed. - task: NuGetAuthenticate@1 displayName: 'Authenticate to AzDO Feeds' # Signing validation will optionally work with the buildmanifest file which is downloaded from # Azure DevOps above. - task: PowerShell@2 displayName: Validate inputs: filePath: eng\common\sdk-task.ps1 arguments: -task SigningValidation -restore -msbuildEngine dotnet /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' /p:SignCheckExclusionsFile='$(System.DefaultWorkingDirectory)/eng/SignCheckExclusionsFile.txt' ${{ parameters.signingValidationAdditionalParameters }} - template: /eng/common/core-templates/steps/publish-logs.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} StageLabel: 'Validation' JobLabel: 'Signing' BinlogToolVersion: $(BinlogToolVersion) # SourceLink validation has been removed — the underlying CLI tool # (targeting netcoreapp2.1) has not functioned for years. # The enableSourceLinkValidation parameter is kept but ignored so # existing pipelines that pass it are not broken. # See https://github.com/dotnet/arcade/issues/16647 - ${{ if eq(parameters.enableSourceLinkValidation, 'true') }}: - job: displayName: 'SourceLink Validation Removed - please remove enableSourceLinkValidation from your pipeline' pool: server steps: - task: Delay@1 displayName: 'Warning: SourceLink validation removed (see https://github.com/dotnet/arcade/issues/16647)' inputs: delayForMinutes: '0' - ${{ if ne(parameters.publishAssetsImmediately, 'true') }}: - stage: publish_using_darc ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true')) }}: dependsOn: ${{ parameters.publishDependsOn }} ${{ else }}: dependsOn: ${{ parameters.validateDependsOn }} displayName: Publish using Darc variables: - template: /eng/common/core-templates/post-build/common-variables.yml - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} jobs: - job: displayName: Publish Using Darc timeoutInMinutes: 120 pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO image: 1ESPT-Windows2025 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: NetCore1ESPool-Publishing-Internal image: windows.vs2026.amd64 os: windows ${{ else }}: name: NetCore1ESPool-Publishing-Internal demands: ImageOverride -equals windows.vs2026.amd64 steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - task: NuGetAuthenticate@1 # Populate internal runtime variables. - template: /eng/common/templates/steps/enable-internal-sources.yml parameters: legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw) - template: /eng/common/templates/steps/enable-internal-runtimes.yml - task: UseDotNet@2 inputs: version: 8.0.x - task: AzureCLI@2 displayName: Publish Using Darc inputs: azureSubscription: "Darc: Maestro Production" scriptType: ps scriptLocation: scriptPath scriptPath: $(System.DefaultWorkingDirectory)/eng/common/post-build/publish-using-darc.ps1 arguments: > -BuildId $(BARBuildId) -PublishingInfraVersion 3 -AzdoToken '$(System.AccessToken)' -WaitPublishingFinish true -RequireDefaultChannels ${{ parameters.requireDefaultChannels }} -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' -SkipAssetsPublishing '${{ parameters.isAssetlessBuild }}' -runtimeSourceFeed https://ci.dot.net/internal -runtimeSourceFeedKey '$(dotnetbuilds-internal-container-read-token-base64)' ================================================ FILE: eng/common/core-templates/post-build/setup-maestro-vars.yml ================================================ parameters: BARBuildId: '' PromoteToChannelIds: '' is1ESPipeline: '' steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if eq(coalesce(parameters.PromoteToChannelIds, 0), 0) }}: - task: DownloadPipelineArtifact@2 displayName: Download Release Configs inputs: artifactName: ReleaseConfigs targetPath: '$(Build.StagingDirectory)/ReleaseConfigs' - task: AzureCLI@2 name: setReleaseVars displayName: Set Release Configs Vars inputs: azureSubscription: "Darc: Maestro Production" scriptType: pscore scriptLocation: inlineScript inlineScript: | try { if (!$Env:PromoteToMaestroChannels -or $Env:PromoteToMaestroChannels.Trim() -eq '') { $Content = Get-Content $(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt $BarId = $Content | Select -Index 0 $Channels = $Content | Select -Index 1 $IsStableBuild = $Content | Select -Index 2 $AzureDevOpsProject = $Env:System_TeamProject $AzureDevOpsBuildDefinitionId = $Env:System_DefinitionId $AzureDevOpsBuildId = $Env:Build_BuildId } else { . $(System.DefaultWorkingDirectory)\eng\common\tools.ps1 $darc = Get-Darc $buildInfo = & $darc get-build ` --id ${{ parameters.BARBuildId }} ` --extended ` --output-format json ` --ci ` | convertFrom-Json $BarId = ${{ parameters.BARBuildId }} $Channels = $Env:PromoteToMaestroChannels -split "," $Channels = $Channels -join "][" $Channels = "[$Channels]" $IsStableBuild = $buildInfo.stable $AzureDevOpsProject = $buildInfo.azureDevOpsProject $AzureDevOpsBuildDefinitionId = $buildInfo.azureDevOpsBuildDefinitionId $AzureDevOpsBuildId = $buildInfo.azureDevOpsBuildId } Write-Host "##vso[task.setvariable variable=BARBuildId]$BarId" Write-Host "##vso[task.setvariable variable=TargetChannels]$Channels" Write-Host "##vso[task.setvariable variable=IsStableBuild]$IsStableBuild" Write-Host "##vso[task.setvariable variable=AzDOProjectName]$AzureDevOpsProject" Write-Host "##vso[task.setvariable variable=AzDOPipelineId]$AzureDevOpsBuildDefinitionId" Write-Host "##vso[task.setvariable variable=AzDOBuildId]$AzureDevOpsBuildId" } catch { Write-Host $_ Write-Host $_.Exception Write-Host $_.ScriptStackTrace exit 1 } env: PromoteToMaestroChannels: ${{ parameters.PromoteToChannelIds }} ================================================ FILE: eng/common/core-templates/stages/renovate.yml ================================================ # -------------------------------------------------------------------------------------- # Renovate Pipeline Template # -------------------------------------------------------------------------------------- # This template provides a complete reusable pipeline definition for running Renovate # in a 1ES Official pipeline. Pipelines can extend from this template and only need # to pass the Renovate job parameters. # # For more info, see https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md # -------------------------------------------------------------------------------------- parameters: # Path to the Renovate configuration file within the repository. - name: renovateConfigPath type: string default: 'eng/renovate.json' # GitHub repository to run Renovate against, in the format 'owner/repo'. - name: gitHubRepo type: string # List of base branches to target for Renovate PRs. - name: baseBranches type: object default: - main # When true, Renovate will run in dry run mode. - name: dryRun type: boolean default: false # When true, Renovate will recreate PRs even if they were previously closed. - name: forceRecreatePR type: boolean default: false # Name of the arcade repository resource in the pipeline. # This allows repos which haven't been onboarded to Arcade to still use this # template by checking out the repo as a resource with a custom name and pointing # this parameter to it. - name: arcadeRepoResource type: string default: 'self' - name: selfRepoName type: string default: '' - name: arcadeRepoName type: string default: '' # Pool configuration for the pipeline. - name: pool type: object default: name: NetCore1ESPool-Internal image: build.azurelinux.3.amd64 os: linux # Renovate version used in the container image tag. - name: renovateVersion default: 43 type: number # Pool configuration for SDL analysis. - name: sdlPool type: object default: name: NetCore1ESPool-Internal image: windows.vs2026.amd64 os: windows resources: repositories: - repository: 1ESPipelineTemplates type: git name: 1ESPipelineTemplates/1ESPipelineTemplates ref: refs/tags/release extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates parameters: pool: ${{ parameters.pool }} sdl: sourceAnalysisPool: ${{ parameters.sdlPool }} # When repos that aren't onboarded to Arcade use this template, they set the # arcadeRepoResource parameter to point to their Arcade repo resource. In that case, # Aracde will be excluded from SDL analysis. ${{ if ne(parameters.arcadeRepoResource, 'self') }}: sourceRepositoriesToScan: exclude: - repository: ${{ parameters.arcadeRepoResource }} containers: RenovateContainer: image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-renovate-${{ parameters.renovateVersion }}-amd64 stages: - stage: Renovate displayName: Run Renovate jobs: - template: /eng/common/core-templates/job/renovate.yml@${{ parameters.arcadeRepoResource }} parameters: renovateConfigPath: ${{ parameters.renovateConfigPath }} gitHubRepo: ${{ parameters.gitHubRepo }} baseBranches: ${{ parameters.baseBranches }} dryRun: ${{ parameters.dryRun }} forceRecreatePR: ${{ parameters.forceRecreatePR }} pool: ${{ parameters.pool }} arcadeRepoResource: ${{ parameters.arcadeRepoResource }} selfRepoName: ${{ parameters.selfRepoName }} arcadeRepoName: ${{ parameters.arcadeRepoName }} ================================================ FILE: eng/common/core-templates/steps/cleanup-microbuild.yml ================================================ parameters: # Enable cleanup tasks for MicroBuild enableMicrobuild: false # Enable cleanup tasks for MicroBuild on Mac and Linux # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' enableMicrobuildForMacAndLinux: false continueOnError: false steps: - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - task: MicroBuildCleanup@1 displayName: Execute Microbuild cleanup tasks condition: and( always(), or( and( eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test') ), and( ${{ eq(parameters.enableMicrobuildForMacAndLinux, true) }}, ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real') ) )) continueOnError: ${{ parameters.continueOnError }} env: TeamName: $(_TeamName) ================================================ FILE: eng/common/core-templates/steps/enable-internal-runtimes.yml ================================================ # Obtains internal runtime download credentials and populates the 'dotnetbuilds-internal-container-read-token-base64' # variable with the base64-encoded SAS token, by default parameters: - name: federatedServiceConnection type: string default: 'dotnetbuilds-internal-read' - name: outputVariableName type: string default: 'dotnetbuilds-internal-container-read-token-base64' - name: expiryInHours type: number default: 1 - name: base64Encode type: boolean default: true - name: is1ESPipeline type: boolean default: false steps: - ${{ if ne(variables['System.TeamProject'], 'public') }}: - template: /eng/common/core-templates/steps/get-delegation-sas.yml parameters: federatedServiceConnection: ${{ parameters.federatedServiceConnection }} outputVariableName: ${{ parameters.outputVariableName }} expiryInHours: ${{ parameters.expiryInHours }} base64Encode: ${{ parameters.base64Encode }} storageAccount: dotnetbuilds container: internal permissions: rl is1ESPipeline: ${{ parameters.is1ESPipeline }} ================================================ FILE: eng/common/core-templates/steps/enable-internal-sources.yml ================================================ parameters: # This is the Azure federated service connection that we log into to get an access token. - name: nugetFederatedServiceConnection type: string default: 'dnceng-artifacts-feeds-read' - name: is1ESPipeline type: boolean default: false # Legacy parameters to allow for PAT usage - name: legacyCredential type: string default: '' steps: - ${{ if ne(variables['System.TeamProject'], 'public') }}: - ${{ if ne(parameters.legacyCredential, '') }}: - task: PowerShell@2 displayName: Setup Internal Feeds inputs: filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1 arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $Env:Token env: Token: ${{ parameters.legacyCredential }} # If running on dnceng (internal project), just use the default behavior for NuGetAuthenticate. # If running on DevDiv, NuGetAuthenticate is not really an option. It's scoped to a single feed, and we have many feeds that # may be added. Instead, we'll use the traditional approach (add cred to nuget.config), but use an account token. - ${{ else }}: - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - task: PowerShell@2 displayName: Setup Internal Feeds inputs: filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1 arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config - ${{ else }}: - template: /eng/common/templates/steps/get-federated-access-token.yml parameters: federatedServiceConnection: ${{ parameters.nugetFederatedServiceConnection }} outputVariableName: 'dnceng-artifacts-feeds-read-access-token' - task: PowerShell@2 displayName: Setup Internal Feeds inputs: filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1 arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $(dnceng-artifacts-feeds-read-access-token) # This is required in certain scenarios to install the ADO credential provider. # It installed by default in some msbuild invocations (e.g. VS msbuild), but needs to be installed for others # (e.g. dotnet msbuild). - task: NuGetAuthenticate@1 ================================================ FILE: eng/common/core-templates/steps/generate-sbom.yml ================================================ parameters: PackageVersion: unused BuildDropPath: unused PackageName: unused ManifestDirPath: unused IgnoreDirectories: unused sbomContinueOnError: unused is1ESPipeline: unused publishArtifacts: unused steps: - script: | echo "##vso[task.logissue type=warning]Including generate-sbom.yml is deprecated, SBOM generation is handled 1ES PT now. Remove this include." displayName: Issue generate-sbom.yml deprecation warning ================================================ FILE: eng/common/core-templates/steps/get-delegation-sas.yml ================================================ parameters: - name: federatedServiceConnection type: string - name: outputVariableName type: string - name: expiryInHours type: number default: 1 - name: base64Encode type: boolean default: false - name: storageAccount type: string - name: container type: string - name: permissions type: string default: 'rl' - name: is1ESPipeline type: boolean default: false steps: - task: AzureCLI@2 displayName: 'Generate delegation SAS Token for ${{ parameters.storageAccount }}/${{ parameters.container }}' inputs: azureSubscription: ${{ parameters.federatedServiceConnection }} scriptType: 'pscore' scriptLocation: 'inlineScript' inlineScript: | # Calculate the expiration of the SAS token and convert to UTC $expiry = (Get-Date).AddHours(${{ parameters.expiryInHours }}).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") $sas = az storage container generate-sas --account-name ${{ parameters.storageAccount }} --name ${{ parameters.container }} --permissions ${{ parameters.permissions }} --expiry $expiry --auth-mode login --as-user -o tsv if ($LASTEXITCODE -ne 0) { Write-Error "Failed to generate SAS token." exit 1 } if ('${{ parameters.base64Encode }}' -eq 'true') { $sas = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($sas)) } Write-Host "Setting '${{ parameters.outputVariableName }}' with the access token value" Write-Host "##vso[task.setvariable variable=${{ parameters.outputVariableName }};issecret=true]$sas" ================================================ FILE: eng/common/core-templates/steps/get-federated-access-token.yml ================================================ parameters: - name: federatedServiceConnection type: string - name: outputVariableName type: string - name: is1ESPipeline type: boolean - name: stepName type: string default: 'getFederatedAccessToken' - name: condition type: string default: '' # Resource to get a token for. Common values include: # - '499b84ac-1321-427f-aa17-267ca6975798' for Azure DevOps # - 'https://storage.azure.com/' for storage # Defaults to Azure DevOps - name: resource type: string default: '499b84ac-1321-427f-aa17-267ca6975798' - name: isStepOutputVariable type: boolean default: false steps: - task: AzureCLI@2 displayName: 'Getting federated access token for feeds' name: ${{ parameters.stepName }} ${{ if ne(parameters.condition, '') }}: condition: ${{ parameters.condition }} inputs: azureSubscription: ${{ parameters.federatedServiceConnection }} scriptType: 'pscore' scriptLocation: 'inlineScript' inlineScript: | $accessToken = az account get-access-token --query accessToken --resource ${{ parameters.resource }} --output tsv if ($LASTEXITCODE -ne 0) { Write-Error "Failed to get access token for resource '${{ parameters.resource }}'" exit 1 } Write-Host "Setting '${{ parameters.outputVariableName }}' with the access token value" Write-Host "##vso[task.setvariable variable=${{ parameters.outputVariableName }};issecret=true;isOutput=${{ parameters.isStepOutputVariable }}]$accessToken" ================================================ FILE: eng/common/core-templates/steps/install-microbuild-impl.yml ================================================ parameters: - name: microbuildTaskInputs type: object default: {} - name: microbuildEnv type: object default: {} - name: enablePreviewMicrobuild type: boolean default: false - name: condition type: string - name: continueOnError type: boolean steps: - ${{ if eq(parameters.enablePreviewMicrobuild, true) }}: - task: MicroBuildSigningPluginPreview@4 displayName: Install Preview MicroBuild plugin inputs: ${{ parameters.microbuildTaskInputs }} env: ${{ parameters.microbuildEnv }} continueOnError: ${{ parameters.continueOnError }} condition: ${{ parameters.condition }} - ${{ else }}: - task: MicroBuildSigningPlugin@4 displayName: Install MicroBuild plugin inputs: ${{ parameters.microbuildTaskInputs }} env: ${{ parameters.microbuildEnv }} continueOnError: ${{ parameters.continueOnError }} condition: ${{ parameters.condition }} ================================================ FILE: eng/common/core-templates/steps/install-microbuild.yml ================================================ parameters: # Enable install tasks for MicroBuild enableMicrobuild: false # Enable install tasks for MicroBuild on Mac and Linux # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' enableMicrobuildForMacAndLinux: false # Enable preview version of MB signing plugin enablePreviewMicrobuild: false # Determines whether the ESRP service connection information should be passed to the signing plugin. # This overlaps with _SignType to some degree. We only need the service connection for real signing. # It's important that the service connection not be passed to the MicroBuildSigningPlugin task in this place. # Doing so will cause the service connection to be authorized for the pipeline, which isn't allowed and won't work for non-prod. # Unfortunately, _SignType can't be used to exclude the use of the service connection in non-real sign scenarios. The # variable is not available in template expression. _SignType has a very large proliferation across .NET, so replacing it is tough. microbuildUseESRP: true # Microbuild installation directory microBuildOutputFolder: $(Agent.TempDirectory)/MicroBuild # Microbuild version microbuildPluginVersion: 'latest' continueOnError: false steps: - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, 'true') }}: # Needed to download the MicroBuild plugin nupkgs on Mac and Linux when nuget.exe is unavailable - task: UseDotNet@2 displayName: Install .NET 8.0 SDK for MicroBuild Plugin inputs: packageType: sdk version: 8.0.x installationPath: ${{ parameters.microBuildOutputFolder }}/.dotnet-microbuild condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) - script: | set -euo pipefail # UseDotNet@2 prepends the dotnet executable path to the PATH variable, so we can call dotnet directly version=$(dotnet --version) cat << 'EOF' > ${{ parameters.microBuildOutputFolder }}/global.json { "sdk": { "version": "$version", "paths": [ "${{ parameters.microBuildOutputFolder }}/.dotnet-microbuild" ], "errorMessage": "The .NET SDK version $version is required to install the MicroBuild signing plugin." } } EOF displayName: 'Add global.json to MicroBuild Installation path' workingDirectory: ${{ parameters.microBuildOutputFolder }} condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) - script: | REM Check if ESRP is disabled while SignType is real if /I "${{ parameters.microbuildUseESRP }}"=="false" if /I "$(_SignType)"=="real" ( echo Error: ESRP must be enabled when SignType is real. exit /b 1 ) displayName: 'Validate ESRP usage (Windows)' condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT')) - script: | # Check if ESRP is disabled while SignType is real if [ "${{ parameters.microbuildUseESRP }}" = "false" ] && [ "$(_SignType)" = "real" ]; then echo "Error: ESRP must be enabled when SignType is real." exit 1 fi displayName: 'Validate ESRP usage (Non-Windows)' condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) # Two different MB install steps. This is due to not being able to use the agent OS during # YAML expansion, and Windows vs. Linux/Mac uses different service connections. However, # we can avoid including the MB install step if not enabled at all. This avoids a bunch of # extra pipeline authorizations, since most pipelines do not sign on non-Windows. - template: /eng/common/core-templates/steps/install-microbuild-impl.yml parameters: enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} microbuildTaskInputs: signType: $(_SignType) zipSources: false feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json version: ${{ parameters.microbuildPluginVersion }} ${{ if eq(parameters.microbuildUseESRP, true) }}: ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: ConnectedPMEServiceName: 6cc74545-d7b9-4050-9dfa-ebefcc8961ea ${{ else }}: ConnectedPMEServiceName: 248d384a-b39b-46e3-8ad5-c2c210d5e7ca microbuildEnv: TeamName: $(_TeamName) MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) continueOnError: ${{ parameters.continueOnError }} condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test')) - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, true) }}: - template: /eng/common/core-templates/steps/install-microbuild-impl.yml parameters: enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} microbuildTaskInputs: signType: $(_SignType) zipSources: false feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json version: ${{ parameters.microbuildPluginVersion }} workingDirectory: ${{ parameters.microBuildOutputFolder }} ${{ if eq(parameters.microbuildUseESRP, true) }}: ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: ConnectedPMEServiceName: beb8cb23-b303-4c95-ab26-9e44bc958d39 ${{ else }}: ConnectedPMEServiceName: c24de2a5-cc7a-493d-95e4-8e5ff5cad2bc microbuildEnv: TeamName: $(_TeamName) MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) continueOnError: ${{ parameters.continueOnError }} condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real')) ================================================ FILE: eng/common/core-templates/steps/publish-build-artifacts.yml ================================================ parameters: - name: is1ESPipeline type: boolean default: false - name: args type: object default: {} steps: - ${{ if ne(parameters.is1ESPipeline, true) }}: - template: /eng/common/templates/steps/publish-build-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} ${{ each parameter in parameters.args }}: ${{ parameter.key }}: ${{ parameter.value }} - ${{ else }}: - template: /eng/common/templates-official/steps/publish-build-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} ${{ each parameter in parameters.args }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/core-templates/steps/publish-logs.yml ================================================ parameters: StageLabel: '' JobLabel: '' CustomSensitiveDataList: '' # A default - in case value from eng/common/core-templates/post-build/common-variables.yml is not passed BinlogToolVersion: '1.0.11' is1ESPipeline: false steps: - task: Powershell@2 displayName: Prepare Binlogs to Upload inputs: targetType: inline script: | New-Item -ItemType Directory $(System.DefaultWorkingDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ Move-Item -Path $(System.DefaultWorkingDirectory)/artifacts/log/Debug/* $(System.DefaultWorkingDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ continueOnError: true condition: always() - task: PowerShell@2 displayName: Redact Logs inputs: filePath: $(System.DefaultWorkingDirectory)/eng/common/post-build/redact-logs.ps1 # For now this needs to have explicit list of all sensitive data. Taken from eng/publishing/v3/publish.yml # Sensitive data can as well be added to $(System.DefaultWorkingDirectory)/eng/BinlogSecretsRedactionFile.txt' # If the file exists - sensitive data for redaction will be sourced from it # (single entry per line, lines starting with '# ' are considered comments and skipped) arguments: -InputPath '$(System.DefaultWorkingDirectory)/PostBuildLogs' -BinlogToolVersion '${{parameters.BinlogToolVersion}}' -TokensFilePath '$(System.DefaultWorkingDirectory)/eng/BinlogSecretsRedactionFile.txt' -runtimeSourceFeed https://ci.dot.net/internal -runtimeSourceFeedKey '$(dotnetbuilds-internal-container-read-token-base64)' '$(publishing-dnceng-devdiv-code-r-build-re)' '$(dn-bot-all-orgs-artifact-feeds-rw)' '$(akams-client-id)' '$(microsoft-symbol-server-pat)' '$(symweb-symbol-server-pat)' '$(dnceng-symbol-server-pat)' '$(dn-bot-all-orgs-build-rw-code-rw)' '$(System.AccessToken)' ${{parameters.CustomSensitiveDataList}} continueOnError: true condition: always() - task: CopyFiles@2 displayName: Gather post build logs inputs: SourceFolder: '$(System.DefaultWorkingDirectory)/PostBuildLogs' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/PostBuildLogs' condition: always() - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish Logs targetPath: '$(Build.ArtifactStagingDirectory)/PostBuildLogs' artifactName: PostBuildLogs_${{ parameters.StageLabel }}_${{ parameters.JobLabel }}_Attempt$(System.JobAttempt) continueOnError: true condition: always() retryCountOnTaskFailure: 10 # for any files being locked isProduction: false # logs are non-production artifacts ================================================ FILE: eng/common/core-templates/steps/publish-pipeline-artifacts.yml ================================================ parameters: - name: is1ESPipeline type: boolean default: false - name: args type: object default: {} steps: - ${{ if ne(parameters.is1ESPipeline, true) }}: - template: /eng/common/templates/steps/publish-pipeline-artifacts.yml parameters: ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} - ${{ else }}: - template: /eng/common/templates-official/steps/publish-pipeline-artifacts.yml parameters: ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/core-templates/steps/retain-build.yml ================================================ parameters: # Optional azure devops PAT with build execute permissions for the build's organization, # only needed if the build that should be retained ran on a different organization than # the pipeline where this template is executing from Token: '' # Optional BuildId to retain, defaults to the current running build BuildId: '' # Azure devops Organization URI for the build in the https://dev.azure.com/ format. # Defaults to the organization the current pipeline is running on AzdoOrgUri: '$(System.CollectionUri)' # Azure devops project for the build. Defaults to the project the current pipeline is running on AzdoProject: '$(System.TeamProject)' steps: - task: powershell@2 inputs: targetType: 'filePath' filePath: eng/common/retain-build.ps1 pwsh: true arguments: > -AzdoOrgUri: ${{parameters.AzdoOrgUri}} -AzdoProject ${{parameters.AzdoProject}} -Token ${{coalesce(parameters.Token, '$env:SYSTEM_ACCESSTOKEN') }} -BuildId ${{coalesce(parameters.BuildId, '$env:BUILD_ID')}} displayName: Enable permanent build retention env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) BUILD_ID: $(Build.BuildId) ================================================ FILE: eng/common/core-templates/steps/send-to-helix.yml ================================================ # Please remember to update the documentation if you make changes to these parameters! parameters: HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number HelixTargetQueues: '' # required -- semicolon-delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group HelixProjectPath: 'eng/common/helixpublish.proj' # optional -- path to the project file to build relative to BUILD_SOURCESDIRECTORY HelixProjectArguments: '' # optional -- arguments passed to the build command HelixConfiguration: '' # optional -- additional property attached to a job HelixPreCommands: '' # optional -- commands to run before Helix work item execution HelixPostCommands: '' # optional -- commands to run after Helix work item execution WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload XUnitProjects: '' # optional -- semicolon-delimited list of XUnitProjects to parse and send to Helix; requires XUnitRuntimeTargetFramework, XUnitPublishTargetFramework, XUnitRunnerVersion, and IncludeDotNetCli=true XUnitWorkItemTimeout: '' # optional -- the workitem timeout in seconds for all workitems created from the xUnit projects specified by XUnitProjects XUnitPublishTargetFramework: '' # optional -- framework to use to publish your xUnit projects XUnitRuntimeTargetFramework: '' # optional -- framework to use for the xUnit console runner XUnitRunnerVersion: '' # optional -- version of the xUnit nuget package you wish to use on Helix; required for XUnitProjects IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion DotNetCliPackageType: '' # optional -- either 'sdk', 'runtime' or 'aspnetcore-runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting https://helix.int-dot.net ) Creator: '' # optional -- if the build is external, use this to specify who is sending the job DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false steps: - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY/${{ parameters.HelixProjectPath }} /restore /p:TreatWarningsAsErrors=false ${{ parameters.HelixProjectArguments }} /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"' displayName: ${{ parameters.DisplayNamePrefix }} (Windows) env: BuildConfig: $(_BuildConfig) HelixSource: ${{ parameters.HelixSource }} HelixType: ${{ parameters.HelixType }} HelixBuild: ${{ parameters.HelixBuild }} HelixConfiguration: ${{ parameters.HelixConfiguration }} HelixTargetQueues: ${{ parameters.HelixTargetQueues }} HelixAccessToken: ${{ parameters.HelixAccessToken }} HelixPreCommands: ${{ parameters.HelixPreCommands }} HelixPostCommands: ${{ parameters.HelixPostCommands }} WorkItemDirectory: ${{ parameters.WorkItemDirectory }} WorkItemCommand: ${{ parameters.WorkItemCommand }} WorkItemTimeout: ${{ parameters.WorkItemTimeout }} CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} XUnitProjects: ${{ parameters.XUnitProjects }} XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} DotNetCliVersion: ${{ parameters.DotNetCliVersion }} WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} HelixBaseUri: ${{ parameters.HelixBaseUri }} Creator: ${{ parameters.Creator }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) continueOnError: ${{ parameters.continueOnError }} - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/${{ parameters.HelixProjectPath }} /restore /p:TreatWarningsAsErrors=false ${{ parameters.HelixProjectArguments }} /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog displayName: ${{ parameters.DisplayNamePrefix }} (Unix) env: BuildConfig: $(_BuildConfig) HelixSource: ${{ parameters.HelixSource }} HelixType: ${{ parameters.HelixType }} HelixBuild: ${{ parameters.HelixBuild }} HelixConfiguration: ${{ parameters.HelixConfiguration }} HelixTargetQueues: ${{ parameters.HelixTargetQueues }} HelixAccessToken: ${{ parameters.HelixAccessToken }} HelixPreCommands: ${{ parameters.HelixPreCommands }} HelixPostCommands: ${{ parameters.HelixPostCommands }} WorkItemDirectory: ${{ parameters.WorkItemDirectory }} WorkItemCommand: ${{ parameters.WorkItemCommand }} WorkItemTimeout: ${{ parameters.WorkItemTimeout }} CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} XUnitProjects: ${{ parameters.XUnitProjects }} XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} DotNetCliVersion: ${{ parameters.DotNetCliVersion }} WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} HelixBaseUri: ${{ parameters.HelixBaseUri }} Creator: ${{ parameters.Creator }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) continueOnError: ${{ parameters.continueOnError }} ================================================ FILE: eng/common/core-templates/steps/source-build.yml ================================================ parameters: # This template adds arcade-powered source-build to CI. # This is a 'steps' template, and is intended for advanced scenarios where the existing build # infra has a careful build methodology that must be followed. For example, a repo # (dotnet/runtime) might choose to clone the GitHub repo only once and store it as a pipeline # artifact for all subsequent jobs to use, to reduce dependence on a strong network connection to # GitHub. Using this steps template leaves room for that infra to be included. # Defines the platform on which to run the steps. See 'eng/common/core-templates/job/source-build.yml' # for details. The entire object is described in the 'job' template for simplicity, even though # the usage of the properties on this object is split between the 'job' and 'steps' templates. platform: {} is1ESPipeline: false steps: # Build. Keep it self-contained for simple reusability. (No source-build-specific job variables.) - script: | set -x df -h # If building on the internal project, the internal storage variable may be available (usually only if needed) # In that case, add variables to allow the download of internal runtimes if the specified versions are not found # in the default public locations. internalRuntimeDownloadArgs= if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://ci.dot.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://ci.dot.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' fi buildConfig=Release # Check if AzDO substitutes in a build config from a variable, and use it if so. if [ '$(_BuildConfig)' != '$''(_BuildConfig)' ]; then buildConfig='$(_BuildConfig)' fi targetRidArgs= if [ '${{ parameters.platform.targetRID }}' != '' ]; then targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}' fi portableBuildArgs= if [ '${{ parameters.platform.portableBuild }}' != '' ]; then portableBuildArgs='/p:PortableBuild=${{ parameters.platform.portableBuild }}' fi ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ --configuration $buildConfig \ --restore --build --pack -bl \ --source-build \ ${{ parameters.platform.buildArguments }} \ $internalRuntimeDownloadArgs \ $targetRidArgs \ $portableBuildArgs \ displayName: Build - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish BuildLogs targetPath: artifacts/log/${{ coalesce(variables._BuildConfig, 'Release') }} artifactName: BuildLogs_SourceBuild_${{ parameters.platform.name }}_Attempt$(System.JobAttempt) continueOnError: true condition: succeededOrFailed() isProduction: false # logs are non-production artifacts ================================================ FILE: eng/common/core-templates/steps/source-index-stage1-publish.yml ================================================ parameters: sourceIndexUploadPackageVersion: 2.0.0-20250906.1 sourceIndexProcessBinlogPackageVersion: 1.0.1-20250906.1 sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json binlogPath: artifacts/log/Debug/Build.binlog steps: - task: UseDotNet@2 displayName: "Source Index: Use .NET 9 SDK" inputs: packageType: sdk version: 9.0.x installationPath: $(Agent.TempDirectory)/dotnet workingDirectory: $(Agent.TempDirectory) - script: | $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --source ${{parameters.sourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --source ${{parameters.sourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools displayName: "Source Index: Download netsourceindex Tools" # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. workingDirectory: $(Agent.TempDirectory) - script: $(Agent.TempDirectory)/.source-index/tools/BinLogToSln -i ${{parameters.BinlogPath}} -r $(System.DefaultWorkingDirectory) -n $(Build.Repository.Name) -o .source-index/stage1output displayName: "Source Index: Process Binlog into indexable sln" - ${{ if and(ne(parameters.runAsPublic, 'true'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - task: AzureCLI@2 displayName: "Source Index: Upload Source Index stage1 artifacts to Azure" inputs: azureSubscription: 'SourceDotNet Stage1 Publish' addSpnToEnvironment: true scriptType: 'ps' scriptLocation: 'inlineScript' inlineScript: | $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) -s netsourceindexstage1 -b stage1 ================================================ FILE: eng/common/core-templates/variables/pool-providers.yml ================================================ parameters: is1ESPipeline: false variables: - ${{ if eq(parameters.is1ESPipeline, 'true') }}: - template: /eng/common/templates-official/variables/pool-providers.yml - ${{ else }}: - template: /eng/common/templates/variables/pool-providers.yml ================================================ FILE: eng/common/cross/armel/tizen/tizen.patch ================================================ diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so --- a/usr/lib/libc.so 2016-12-30 23:00:08.284951863 +0900 +++ b/usr/lib/libc.so 2016-12-30 23:00:32.140951815 +0900 @@ -2,4 +2,4 @@ Use the shared library, but some functions are only in the static library, so try that secondarily. */ OUTPUT_FORMAT(elf32-littlearm) -GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux.so.3 ) ) +GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux.so.3 ) ) ================================================ FILE: eng/common/cross/build-android-rootfs.sh ================================================ #!/usr/bin/env bash set -e __NDK_Version=r21 usage() { echo "Creates a toolchain and sysroot used for cross-compiling for Android." echo echo "Usage: $0 [BuildArch] [ApiLevel] [--ndk NDKVersion]" echo echo "BuildArch is the target architecture of Android. Currently only arm64 is supported." echo "ApiLevel is the target Android API level. API levels usually match to Android releases. See https://source.android.com/source/build-numbers.html" echo "NDKVersion is the version of Android NDK. The default is r21. See https://developer.android.com/ndk/downloads/revision_history" echo echo "By default, the toolchain and sysroot will be generated in cross/android-rootfs/toolchain/[BuildArch]. You can change this behavior" echo "by setting the TOOLCHAIN_DIR environment variable" echo echo "By default, the NDK will be downloaded into the cross/android-rootfs/android-ndk-$__NDK_Version directory. If you already have an NDK installation," echo "you can set the NDK_DIR environment variable to have this script use that installation of the NDK." echo "By default, this script will generate a file, android_platform, in the root of the ROOTFS_DIR directory that contains the RID for the supported and tested Android build: android.28-arm64. This file is to replace '/etc/os-release', which is not available for Android." exit 1 } __ApiLevel=28 # The minimum platform for arm64 is API level 21 but the minimum version that support glob(3) is 28. See $ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/glob.h __BuildArch=arm64 __AndroidArch=aarch64 __AndroidToolchain=aarch64-linux-android while :; do if [[ "$#" -le 0 ]]; then break fi i=$1 lowerI="$(echo $i | tr "[:upper:]" "[:lower:]")" case $lowerI in -?|-h|--help) usage exit 1 ;; arm64) __BuildArch=arm64 __AndroidArch=aarch64 __AndroidToolchain=aarch64-linux-android ;; arm) __BuildArch=arm __AndroidArch=arm __AndroidToolchain=arm-linux-androideabi ;; --ndk) shift __NDK_Version=$1 ;; *[0-9]) __ApiLevel=$i ;; *) __UnprocessedBuildArgs="$__UnprocessedBuildArgs $i" ;; esac shift done if [[ "$__NDK_Version" == "r21" ]] || [[ "$__NDK_Version" == "r22" ]]; then __NDK_File_Arch_Spec=-x86_64 __SysRoot=sysroot else __NDK_File_Arch_Spec= __SysRoot=toolchains/llvm/prebuilt/linux-x86_64/sysroot fi # Obtain the location of the bash script to figure out where the root of the repo is. __ScriptBaseDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" __CrossDir="$__ScriptBaseDir/../../../.tools/android-rootfs" if [[ ! -f "$__CrossDir" ]]; then mkdir -p "$__CrossDir" fi # Resolve absolute path to avoid `../` in build logs __CrossDir="$( cd "$__CrossDir" && pwd )" __NDK_Dir="$__CrossDir/android-ndk-$__NDK_Version" __lldb_Dir="$__CrossDir/lldb" __ToolchainDir="$__CrossDir/android-ndk-$__NDK_Version" if [[ -n "$TOOLCHAIN_DIR" ]]; then __ToolchainDir=$TOOLCHAIN_DIR fi if [[ -n "$NDK_DIR" ]]; then __NDK_Dir=$NDK_DIR fi echo "Target API level: $__ApiLevel" echo "Target architecture: $__BuildArch" echo "NDK version: $__NDK_Version" echo "NDK location: $__NDK_Dir" echo "Target Toolchain location: $__ToolchainDir" # Download the NDK if required if [ ! -d $__NDK_Dir ]; then echo Downloading the NDK into $__NDK_Dir mkdir -p $__NDK_Dir wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux$__NDK_File_Arch_Spec.zip -O $__CrossDir/android-ndk-$__NDK_Version-linux.zip unzip -q $__CrossDir/android-ndk-$__NDK_Version-linux.zip -d $__CrossDir fi if [ ! -d $__lldb_Dir ]; then mkdir -p $__lldb_Dir echo Downloading LLDB into $__lldb_Dir wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/lldb-2.3.3614996-linux-x86_64.zip -O $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip unzip -q $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip -d $__lldb_Dir fi echo "Download dependencies..." __TmpDir=$__CrossDir/tmp/$__BuildArch/ mkdir -p "$__TmpDir" # combined dependencies for coreclr, installer and libraries __AndroidPackages="libicu" __AndroidPackages+=" libandroid-glob" __AndroidPackages+=" liblzma" __AndroidPackages+=" krb5" __AndroidPackages+=" openssl" for path in $(wget -qO- https://packages.termux.dev/termux-main-21/dists/stable/main/binary-$__AndroidArch/Packages |\ grep -A15 "Package: \(${__AndroidPackages// /\\|}\)" | grep -v "static\|tool" | grep Filename); do if [[ "$path" != "Filename:" ]]; then echo "Working on: $path" wget -qO- https://packages.termux.dev/termux-main-21/$path | dpkg -x - "$__TmpDir" fi done cp -R "$__TmpDir/data/data/com.termux/files/usr/"* "$__ToolchainDir/$__SysRoot/usr/" # Generate platform file for build.sh script to assign to __DistroRid echo "Generating platform file..." echo "RID=android.${__ApiLevel}-${__BuildArch}" > $__ToolchainDir/$__SysRoot/android_platform echo "Now to build coreclr, libraries and host; run:" echo ROOTFS_DIR=$(realpath $__ToolchainDir/$__SysRoot) ./build.sh clr+libs+host --cross --arch $__BuildArch ================================================ FILE: eng/common/cross/build-rootfs.sh ================================================ #!/usr/bin/env bash set -e usage() { echo "Usage: $0 [BuildArch] [CodeName] [lldbx.y] [llvmx[.y]] [--skipunmount] --rootfsdir ]" echo "BuildArch can be: arm(default), arm64, armel, armv6, loongarch64, ppc64le, riscv64, s390x, x64, x86" echo "CodeName - optional, Code name for Linux, can be: xenial(default), zesty, bionic, alpine" echo " for alpine can be specified with version: alpineX.YY or alpineedge" echo " for FreeBSD can be: freebsd13, freebsd14" echo " for OpenBSD can be: openbsd" echo " for illumos can be: illumos" echo " for Haiku can be: haiku." echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine and FreeBSD" echo "llvmx[.y] - optional, LLVM version for LLVM related packages." echo "--skipunmount - optional, will skip the unmount of rootfs folder." echo "--skipsigcheck - optional, will skip package signature checks (allowing untrusted packages)." echo "--skipemulation - optional, will skip qemu and debootstrap requirement when building environment for debian based systems." echo "--use-mirror - optional, use mirror URL to fetch resources, when available." echo "--jobs N - optional, restrict to N jobs." exit 1 } __CodeName=xenial __CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) __BuildArch=arm __AlpineArch=armv7 __FreeBSDArch=arm __FreeBSDMachineArch=armv7 __OpenBSDArch=arm __OpenBSDMachineArch=armv7 __IllumosArch=arm7 __HaikuArch=arm __QEMUArch=arm __UbuntuArch=armhf __UbuntuRepo= __UbuntuSuites="updates security backports" __LLDB_Package="liblldb-3.9-dev" __SkipUnmount=0 # base development support __UbuntuPackages="build-essential" __AlpinePackages="alpine-base" __AlpinePackages+=" build-base" __AlpinePackages+=" linux-headers" __AlpinePackages+=" lldb-dev" __AlpinePackages+=" python3" __AlpinePackages+=" libedit" # symlinks fixer __UbuntuPackages+=" symlinks" # runtime dependencies __UbuntuPackages+=" libicu-dev" __UbuntuPackages+=" liblttng-ust-dev" __UbuntuPackages+=" libunwind8-dev" __AlpinePackages+=" gettext-dev" __AlpinePackages+=" icu-dev" __AlpinePackages+=" libunwind-dev" __AlpinePackages+=" lttng-ust-dev" __AlpinePackages+=" compiler-rt" # runtime libraries' dependencies __UbuntuPackages+=" libcurl4-openssl-dev" __UbuntuPackages+=" libkrb5-dev" __UbuntuPackages+=" libssl-dev" __UbuntuPackages+=" zlib1g-dev" __UbuntuPackages+=" libbrotli-dev" __AlpinePackages+=" curl-dev" __AlpinePackages+=" krb5-dev" __AlpinePackages+=" openssl-dev" __AlpinePackages+=" zlib-dev" __FreeBSDBase="13.5-RELEASE" __FreeBSDPkg="1.21.3" __FreeBSDABI="13" __FreeBSDPackages="libunwind" __FreeBSDPackages+=" icu" __FreeBSDPackages+=" libinotify" __FreeBSDPackages+=" openssl" __FreeBSDPackages+=" krb5" __FreeBSDPackages+=" terminfo-db" __OpenBSDVersion="7.8" __OpenBSDPackages="heimdal-libs" __OpenBSDPackages+=" icu4c" __OpenBSDPackages+=" inotify-tools" __OpenBSDPackages+=" openssl" __IllumosPackages="icu" __IllumosPackages+=" mit-krb5" __IllumosPackages+=" openssl" __IllumosPackages+=" zlib" __HaikuPackages="gcc_syslibs" __HaikuPackages+=" gcc_syslibs_devel" __HaikuPackages+=" gmp" __HaikuPackages+=" gmp_devel" __HaikuPackages+=" icu[0-9]+" __HaikuPackages+=" icu[0-9]*_devel" __HaikuPackages+=" krb5" __HaikuPackages+=" krb5_devel" __HaikuPackages+=" libiconv" __HaikuPackages+=" libiconv_devel" __HaikuPackages+=" llvm[0-9]*_libunwind" __HaikuPackages+=" llvm[0-9]*_libunwind_devel" __HaikuPackages+=" mpfr" __HaikuPackages+=" mpfr_devel" __HaikuPackages+=" openssl3" __HaikuPackages+=" openssl3_devel" __HaikuPackages+=" zlib" __HaikuPackages+=" zlib_devel" # ML.NET dependencies __UbuntuPackages+=" libomp5" __UbuntuPackages+=" libomp-dev" # Taken from https://github.com/alpinelinux/alpine-chroot-install/blob/6d08f12a8a70dd9b9dc7d997c88aa7789cc03c42/alpine-chroot-install#L85-L133 __AlpineKeys=' 4a6a0840:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1yHJxQgsHQREclQu4Ohe\nqxTxd1tHcNnvnQTu/UrTky8wWvgXT+jpveroeWWnzmsYlDI93eLI2ORakxb3gA2O\nQ0Ry4ws8vhaxLQGC74uQR5+/yYrLuTKydFzuPaS1dK19qJPXB8GMdmFOijnXX4SA\njixuHLe1WW7kZVtjL7nufvpXkWBGjsfrvskdNA/5MfxAeBbqPgaq0QMEfxMAn6/R\nL5kNepi/Vr4S39Xvf2DzWkTLEK8pcnjNkt9/aafhWqFVW7m3HCAII6h/qlQNQKSo\nGuH34Q8GsFG30izUENV9avY7hSLq7nggsvknlNBZtFUcmGoQrtx3FmyYsIC8/R+B\nywIDAQAB 5243ef4b:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvNijDxJ8kloskKQpJdx+\nmTMVFFUGDoDCbulnhZMJoKNkSuZOzBoFC94omYPtxnIcBdWBGnrm6ncbKRlR+6oy\nDO0W7c44uHKCFGFqBhDasdI4RCYP+fcIX/lyMh6MLbOxqS22TwSLhCVjTyJeeH7K\naA7vqk+QSsF4TGbYzQDDpg7+6aAcNzg6InNePaywA6hbT0JXbxnDWsB+2/LLSF2G\nmnhJlJrWB1WGjkz23ONIWk85W4S0XB/ewDefd4Ly/zyIciastA7Zqnh7p3Ody6Q0\nsS2MJzo7p3os1smGjUF158s6m/JbVh4DN6YIsxwl2OjDOz9R0OycfJSDaBVIGZzg\ncQIDAQAB 524d27bb:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr8s1q88XpuJWLCZALdKj\nlN8wg2ePB2T9aIcaxryYE/Jkmtu+ZQ5zKq6BT3y/udt5jAsMrhHTwroOjIsF9DeG\ne8Y3vjz+Hh4L8a7hZDaw8jy3CPag47L7nsZFwQOIo2Cl1SnzUc6/owoyjRU7ab0p\niWG5HK8IfiybRbZxnEbNAfT4R53hyI6z5FhyXGS2Ld8zCoU/R4E1P0CUuXKEN4p0\n64dyeUoOLXEWHjgKiU1mElIQj3k/IF02W89gDj285YgwqA49deLUM7QOd53QLnx+\nxrIrPv3A+eyXMFgexNwCKQU9ZdmWa00MjjHlegSGK8Y2NPnRoXhzqSP9T9i2HiXL\nVQIDAQAB 5261cecb:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwlzMkl7b5PBdfMzGdCT0\ncGloRr5xGgVmsdq5EtJvFkFAiN8Ac9MCFy/vAFmS8/7ZaGOXoCDWbYVLTLOO2qtX\nyHRl+7fJVh2N6qrDDFPmdgCi8NaE+3rITWXGrrQ1spJ0B6HIzTDNEjRKnD4xyg4j\ng01FMcJTU6E+V2JBY45CKN9dWr1JDM/nei/Pf0byBJlMp/mSSfjodykmz4Oe13xB\nCa1WTwgFykKYthoLGYrmo+LKIGpMoeEbY1kuUe04UiDe47l6Oggwnl+8XD1MeRWY\nsWgj8sF4dTcSfCMavK4zHRFFQbGp/YFJ/Ww6U9lA3Vq0wyEI6MCMQnoSMFwrbgZw\nwwIDAQAB 58199dcc:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3v8/ye/V/t5xf4JiXLXa\nhWFRozsnmn3hobON20GdmkrzKzO/eUqPOKTpg2GtvBhK30fu5oY5uN2ORiv2Y2ht\neLiZ9HVz3XP8Fm9frha60B7KNu66FO5P2o3i+E+DWTPqqPcCG6t4Znk2BypILcit\nwiPKTsgbBQR2qo/cO01eLLdt6oOzAaF94NH0656kvRewdo6HG4urbO46tCAizvCR\nCA7KGFMyad8WdKkTjxh8YLDLoOCtoZmXmQAiwfRe9pKXRH/XXGop8SYptLqyVVQ+\ntegOD9wRs2tOlgcLx4F/uMzHN7uoho6okBPiifRX+Pf38Vx+ozXh056tjmdZkCaV\naQIDAQAB 58cbb476:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoSPnuAGKtRIS5fEgYPXD\n8pSGvKAmIv3A08LBViDUe+YwhilSHbYXUEAcSH1KZvOo1WT1x2FNEPBEFEFU1Eyc\n+qGzbA03UFgBNvArurHQ5Z/GngGqE7IarSQFSoqewYRtFSfp+TL9CUNBvM0rT7vz\n2eMu3/wWG+CBmb92lkmyWwC1WSWFKO3x8w+Br2IFWvAZqHRt8oiG5QtYvcZL6jym\nY8T6sgdDlj+Y+wWaLHs9Fc+7vBuyK9C4O1ORdMPW15qVSl4Lc2Wu1QVwRiKnmA+c\nDsH/m7kDNRHM7TjWnuj+nrBOKAHzYquiu5iB3Qmx+0gwnrSVf27Arc3ozUmmJbLj\nzQIDAQAB 58e4f17d:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvBxJN9ErBgdRcPr5g4hV\nqyUSGZEKuvQliq2Z9SRHLh2J43+EdB6A+yzVvLnzcHVpBJ+BZ9RV30EM9guck9sh\nr+bryZcRHyjG2wiIEoduxF2a8KeWeQH7QlpwGhuobo1+gA8L0AGImiA6UP3LOirl\nI0G2+iaKZowME8/tydww4jx5vG132JCOScMjTalRsYZYJcjFbebQQolpqRaGB4iG\nWqhytWQGWuKiB1A22wjmIYf3t96l1Mp+FmM2URPxD1gk/BIBnX7ew+2gWppXOK9j\n1BJpo0/HaX5XoZ/uMqISAAtgHZAqq+g3IUPouxTphgYQRTRYpz2COw3NF43VYQrR\nbQIDAQAB 60ac2099:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwR4uJVtJOnOFGchnMW5Y\nj5/waBdG1u5BTMlH+iQMcV5+VgWhmpZHJCBz3ocD+0IGk2I68S5TDOHec/GSC0lv\n6R9o6F7h429GmgPgVKQsc8mPTPtbjJMuLLs4xKc+viCplXc0Nc0ZoHmCH4da6fCV\ntdpHQjVe6F9zjdquZ4RjV6R6JTiN9v924dGMAkbW/xXmamtz51FzondKC52Gh8Mo\n/oA0/T0KsCMCi7tb4QNQUYrf+Xcha9uus4ww1kWNZyfXJB87a2kORLiWMfs2IBBJ\nTmZ2Fnk0JnHDb8Oknxd9PvJPT0mvyT8DA+KIAPqNvOjUXP4bnjEHJcoCP9S5HkGC\nIQIDAQAB 6165ee59:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAutQkua2CAig4VFSJ7v54\nALyu/J1WB3oni7qwCZD3veURw7HxpNAj9hR+S5N/pNeZgubQvJWyaPuQDm7PTs1+\ntFGiYNfAsiibX6Rv0wci3M+z2XEVAeR9Vzg6v4qoofDyoTbovn2LztaNEjTkB+oK\ntlvpNhg1zhou0jDVYFniEXvzjckxswHVb8cT0OMTKHALyLPrPOJzVtM9C1ew2Nnc\n3848xLiApMu3NBk0JqfcS3Bo5Y2b1FRVBvdt+2gFoKZix1MnZdAEZ8xQzL/a0YS5\nHd0wj5+EEKHfOd3A75uPa/WQmA+o0cBFfrzm69QDcSJSwGpzWrD1ScH3AK8nWvoj\nv7e9gukK/9yl1b4fQQ00vttwJPSgm9EnfPHLAtgXkRloI27H6/PuLoNvSAMQwuCD\nhQRlyGLPBETKkHeodfLoULjhDi1K2gKJTMhtbnUcAA7nEphkMhPWkBpgFdrH+5z4\nLxy+3ek0cqcI7K68EtrffU8jtUj9LFTUC8dERaIBs7NgQ/LfDbDfGh9g6qVj1hZl\nk9aaIPTm/xsi8v3u+0qaq7KzIBc9s59JOoA8TlpOaYdVgSQhHHLBaahOuAigH+VI\nisbC9vmqsThF2QdDtQt37keuqoda2E6sL7PUvIyVXDRfwX7uMDjlzTxHTymvq2Ck\nhtBqojBnThmjJQFgZXocHG8CAwEAAQ== 61666e3f:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlEyxkHggKCXC2Wf5Mzx4\nnZLFZvU2bgcA3exfNPO/g1YunKfQY+Jg4fr6tJUUTZ3XZUrhmLNWvpvSwDS19ZmC\nIXOu0+V94aNgnhMsk9rr59I8qcbsQGIBoHzuAl8NzZCgdbEXkiY90w1skUw8J57z\nqCsMBydAueMXuWqF5nGtYbi5vHwK42PffpiZ7G5Kjwn8nYMW5IZdL6ZnMEVJUWC9\nI4waeKg0yskczYDmZUEAtrn3laX9677ToCpiKrvmZYjlGl0BaGp3cxggP2xaDbUq\nqfFxWNgvUAb3pXD09JM6Mt6HSIJaFc9vQbrKB9KT515y763j5CC2KUsilszKi3mB\nHYe5PoebdjS7D1Oh+tRqfegU2IImzSwW3iwA7PJvefFuc/kNIijfS/gH/cAqAK6z\nbhdOtE/zc7TtqW2Wn5Y03jIZdtm12CxSxwgtCF1NPyEWyIxAQUX9ACb3M0FAZ61n\nfpPrvwTaIIxxZ01L3IzPLpbc44x/DhJIEU+iDt6IMTrHOphD9MCG4631eIdB0H1b\n6zbNX1CXTsafqHRFV9XmYYIeOMggmd90s3xIbEujA6HKNP/gwzO6CDJ+nHFDEqoF\nSkxRdTkEqjTjVKieURW7Swv7zpfu5PrsrrkyGnsRrBJJzXlm2FOOxnbI2iSL1B5F\nrO5kbUxFeZUIDq+7Yv4kLWcCAwEAAQ== 616a9724:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnC+bR4bHf/L6QdU4puhQ\ngl1MHePszRC38bzvVFDUJsmCaMCL2suCs2A2yxAgGb9pu9AJYLAmxQC4mM3jNqhg\n/E7yuaBbek3O02zN/ctvflJ250wZCy+z0ZGIp1ak6pu1j14IwHokl9j36zNfGtfv\nADVOcdpWITFFlPqwq1qt/H3UsKVmtiF3BNWWTeUEQwKvlU8ymxgS99yn0+4OPyNT\nL3EUeS+NQJtDS01unau0t7LnjUXn+XIneWny8bIYOQCuVR6s/gpIGuhBaUqwaJOw\n7jkJZYF2Ij7uPb4b5/R3vX2FfxxqEHqssFSg8FFUNTZz3qNZs0CRVyfA972g9WkJ\nhPfn31pQYil4QGRibCMIeU27YAEjXoqfJKEPh4UWMQsQLrEfdGfb8VgwrPbniGfU\nL3jKJR3VAafL9330iawzVQDlIlwGl6u77gEXMl9K0pfazunYhAp+BMP+9ot5ckK+\nosmrqj11qMESsAj083GeFdfV3pXEIwUytaB0AKEht9DbqUfiE/oeZ/LAXgySMtVC\nsbC4ESmgVeY2xSBIJdDyUap7FR49GGrw0W49NUv9gRgQtGGaNVQQO9oGL2PBC41P\niWF9GLoX30HIz1P8PF/cZvicSSPkQf2Z6TV+t0ebdGNS5DjapdnCrq8m9Z0pyKsQ\nuxAL2a7zX8l5i1CZh1ycUGsCAwEAAQ== 616abc23:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0MfCDrhODRCIxR9Dep1s\neXafh5CE5BrF4WbCgCsevyPIdvTeyIaW4vmO3bbG4VzhogDZju+R3IQYFuhoXP5v\nY+zYJGnwrgz3r5wYAvPnLEs1+dtDKYOgJXQj+wLJBW1mzRDL8FoRXOe5iRmn1EFS\nwZ1DoUvyu7/J5r0itKicZp3QKED6YoilXed+1vnS4Sk0mzN4smuMR9eO1mMCqNp9\n9KTfRDHTbakIHwasECCXCp50uXdoW6ig/xUAFanpm9LtK6jctNDbXDhQmgvAaLXZ\nLvFqoaYJ/CvWkyYCgL6qxvMvVmPoRv7OPcyni4xR/WgWa0MSaEWjgPx3+yj9fiMA\n1S02pFWFDOr5OUF/O4YhFJvUCOtVsUPPfA/Lj6faL0h5QI9mQhy5Zb9TTaS9jB6p\nLw7u0dJlrjFedk8KTJdFCcaGYHP6kNPnOxMylcB/5WcztXZVQD5WpCicGNBxCGMm\nW64SgrV7M07gQfL/32QLsdqPUf0i8hoVD8wfQ3EpbQzv6Fk1Cn90bZqZafg8XWGY\nwddhkXk7egrr23Djv37V2okjzdqoyLBYBxMz63qQzFoAVv5VoY2NDTbXYUYytOvG\nGJ1afYDRVWrExCech1mX5ZVUB1br6WM+psFLJFoBFl6mDmiYt0vMYBddKISsvwLl\nIJQkzDwtXzT2cSjoj3T5QekCAwEAAQ== 616ac3bc:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvaaoSLab+IluixwKV5Od\n0gib2YurjPatGIbn5Ov2DLUFYiebj2oJINXJSwUOO+4WcuHFEqiL/1rya+k5hLZt\nhnPL1tn6QD4rESznvGSasRCQNT2vS/oyZbTYJRyAtFkEYLlq0t3S3xBxxHWuvIf0\nqVxVNYpQWyM3N9RIeYBR/euXKJXileSHk/uq1I5wTC0XBIHWcthczGN0m9wBEiWS\n0m3cnPk4q0Ea8mUJ91Rqob19qETz6VbSPYYpZk3qOycjKosuwcuzoMpwU8KRiMFd\n5LHtX0Hx85ghGsWDVtS0c0+aJa4lOMGvJCAOvDfqvODv7gKlCXUpgumGpLdTmaZ8\n1RwqspAe3IqBcdKTqRD4m2mSg23nVx2FAY3cjFvZQtfooT7q1ItRV5RgH6FhQSl7\n+6YIMJ1Bf8AAlLdRLpg+doOUGcEn+pkDiHFgI8ylH1LKyFKw+eXaAml/7DaWZk1d\ndqggwhXOhc/UUZFQuQQ8A8zpA13PcbC05XxN2hyP93tCEtyynMLVPtrRwDnHxFKa\nqKzs3rMDXPSXRn3ZZTdKH3069ApkEjQdpcwUh+EmJ1Ve/5cdtzT6kKWCjKBFZP/s\n91MlRrX2BTRdHaU5QJkUheUtakwxuHrdah2F94lRmsnQlpPr2YseJu6sIE+Dnx4M\nCfhdVbQL2w54R645nlnohu8CAwEAAQ== 616adfeb:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq0BFD1D4lIxQcsqEpQzU\npNCYM3aP1V/fxxVdT4DWvSI53JHTwHQamKdMWtEXetWVbP5zSROniYKFXd/xrD9X\n0jiGHey3lEtylXRIPxe5s+wXoCmNLcJVnvTcDtwx/ne2NLHxp76lyc25At+6RgE6\nADjLVuoD7M4IFDkAsd8UQ8zM0Dww9SylIk/wgV3ZkifecvgUQRagrNUdUjR56EBZ\nraQrev4hhzOgwelT0kXCu3snbUuNY/lU53CoTzfBJ5UfEJ5pMw1ij6X0r5S9IVsy\nKLWH1hiO0NzU2c8ViUYCly4Fe9xMTFc6u2dy/dxf6FwERfGzETQxqZvSfrRX+GLj\n/QZAXiPg5178hT/m0Y3z5IGenIC/80Z9NCi+byF1WuJlzKjDcF/TU72zk0+PNM/H\nKuppf3JT4DyjiVzNC5YoWJT2QRMS9KLP5iKCSThwVceEEg5HfhQBRT9M6KIcFLSs\nmFjx9kNEEmc1E8hl5IR3+3Ry8G5/bTIIruz14jgeY9u5jhL8Vyyvo41jgt9sLHR1\n/J1TxKfkgksYev7PoX6/ZzJ1ksWKZY5NFoDXTNYUgzFUTOoEaOg3BAQKadb3Qbbq\nXIrxmPBdgrn9QI7NCgfnAY3Tb4EEjs3ON/BNyEhUENcXOH6I1NbcuBQ7g9P73kE4\nVORdoc8MdJ5eoKBpO8Ww8HECAwEAAQ== 616ae350:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyduVzi1mWm+lYo2Tqt/0\nXkCIWrDNP1QBMVPrE0/ZlU2bCGSoo2Z9FHQKz/mTyMRlhNqTfhJ5qU3U9XlyGOPJ\npiM+b91g26pnpXJ2Q2kOypSgOMOPA4cQ42PkHBEqhuzssfj9t7x47ppS94bboh46\nxLSDRff/NAbtwTpvhStV3URYkxFG++cKGGa5MPXBrxIp+iZf9GnuxVdST5PGiVGP\nODL/b69sPJQNbJHVquqUTOh5Ry8uuD2WZuXfKf7/C0jC/ie9m2+0CttNu9tMciGM\nEyKG1/Xhk5iIWO43m4SrrT2WkFlcZ1z2JSf9Pjm4C2+HovYpihwwdM/OdP8Xmsnr\nDzVB4YvQiW+IHBjStHVuyiZWc+JsgEPJzisNY0Wyc/kNyNtqVKpX6dRhMLanLmy+\nf53cCSI05KPQAcGj6tdL+D60uKDkt+FsDa0BTAobZ31OsFVid0vCXtsbplNhW1IF\nHwsGXBTVcfXg44RLyL8Lk/2dQxDHNHzAUslJXzPxaHBLmt++2COa2EI1iWlvtznk\nOk9WP8SOAIj+xdqoiHcC4j72BOVVgiITIJNHrbppZCq6qPR+fgXmXa+sDcGh30m6\n9Wpbr28kLMSHiENCWTdsFij+NQTd5S47H7XTROHnalYDuF1RpS+DpQidT5tUimaT\nJZDr++FjKrnnijbyNF8b98UCAwEAAQ== 616db30d:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpUpyWDWjlUk3smlWeA0\nlIMW+oJ38t92CRLHH3IqRhyECBRW0d0aRGtq7TY8PmxjjvBZrxTNDpJT6KUk4LRm\na6A6IuAI7QnNK8SJqM0DLzlpygd7GJf8ZL9SoHSH+gFsYF67Cpooz/YDqWrlN7Vw\ntO00s0B+eXy+PCXYU7VSfuWFGK8TGEv6HfGMALLjhqMManyvfp8hz3ubN1rK3c8C\nUS/ilRh1qckdbtPvoDPhSbTDmfU1g/EfRSIEXBrIMLg9ka/XB9PvWRrekrppnQzP\nhP9YE3x/wbFc5QqQWiRCYyQl/rgIMOXvIxhkfe8H5n1Et4VAorkpEAXdsfN8KSVv\nLSMazVlLp9GYq5SUpqYX3KnxdWBgN7BJoZ4sltsTpHQ/34SXWfu3UmyUveWj7wp0\nx9hwsPirVI00EEea9AbP7NM2rAyu6ukcm4m6ATd2DZJIViq2es6m60AE6SMCmrQF\nwmk4H/kdQgeAELVfGOm2VyJ3z69fQuywz7xu27S6zTKi05Qlnohxol4wVb6OB7qG\nLPRtK9ObgzRo/OPumyXqlzAi/Yvyd1ZQk8labZps3e16bQp8+pVPiumWioMFJDWV\nGZjCmyMSU8V6MB6njbgLHoyg2LCukCAeSjbPGGGYhnKLm1AKSoJh3IpZuqcKCk5C\n8CM1S15HxV78s9dFntEqIokCAwEAAQ== 66ba20fe:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtfB12w4ZgqsXWZDfUAV/\n6Y4aHUKIu3q4SXrNZ7CXF9nXoAVYrS7NAxJdAodsY3vPCN0g5O8DFXR+390LdOuQ\n+HsGKCc1k5tX5ZXld37EZNTNSbR0k+NKhd9h6X3u6wqPOx7SIKxwAQR8qeeFq4pP\nrt9GAGlxtuYgzIIcKJPwE0dZlcBCg+GnptCUZXp/38BP1eYC+xTXSL6Muq1etYfg\nodXdb7Yl+2h1IHuOwo5rjgY5kpY7GcAs8AjGk3lDD/av60OTYccknH0NCVSmPoXK\nvrxDBOn0LQRNBLcAfnTKgHrzy0Q5h4TNkkyTgxkoQw5ObDk9nnabTxql732yy9BY\ns+hM9+dSFO1HKeVXreYSA2n1ndF18YAvAumzgyqzB7I4pMHXq1kC/8bONMJxwSkS\nYm6CoXKyavp7RqGMyeVpRC7tV+blkrrUml0BwNkxE+XnwDRB3xDV6hqgWe0XrifD\nYTfvd9ScZQP83ip0r4IKlq4GMv/R5shcCRJSkSZ6QSGshH40JYSoiwJf5FHbj9ND\n7do0UAqebWo4yNx63j/wb2ULorW3AClv0BCFSdPsIrCStiGdpgJDBR2P2NZOCob3\nG9uMj+wJD6JJg2nWqNJxkANXX37Qf8plgzssrhrgOvB0fjjS7GYhfkfmZTJ0wPOw\nA8+KzFseBh4UFGgue78KwgkCAwEAAQ== ' __Keyring= __KeyringFile="/usr/share/keyrings/ubuntu-archive-keyring.gpg" __SkipSigCheck=0 __SkipEmulation=0 __UseMirror=0 __UnprocessedBuildArgs= while :; do if [[ "$#" -le 0 ]]; then break fi lowerI="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case $lowerI in -\?|-h|--help) usage ;; arm) __BuildArch=arm __UbuntuArch=armhf __AlpineArch=armv7 __QEMUArch=arm ;; arm64) __BuildArch=arm64 __UbuntuArch=arm64 __AlpineArch=aarch64 __QEMUArch=aarch64 __FreeBSDArch=arm64 __FreeBSDMachineArch=aarch64 __OpenBSDArch=arm64 __OpenBSDMachineArch=aarch64 ;; armel) __BuildArch=armel __UbuntuArch=armel __UbuntuRepo="http://archive.debian.org/debian/" __CodeName=buster __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" __LLDB_Package="liblldb-6.0-dev" __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp5/}" __UbuntuSuites= ;; armv6) __BuildArch=armv6 __UbuntuArch=armhf __QEMUArch=arm __UbuntuRepo="http://raspbian.raspberrypi.org/raspbian/" __CodeName=buster __KeyringFile="/usr/share/keyrings/raspbian-archive-keyring.gpg" __LLDB_Package="liblldb-6.0-dev" __UbuntuSuites= if [[ -e "$__KeyringFile" ]]; then __Keyring="--keyring $__KeyringFile" fi ;; loongarch64) __BuildArch=loongarch64 __AlpineArch=loongarch64 __QEMUArch=loongarch64 __UbuntuArch=loong64 __UbuntuSuites=unreleased __LLDB_Package="liblldb-19-dev" if [[ "$__CodeName" == "sid" ]]; then __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/" fi ;; riscv64) __BuildArch=riscv64 __AlpineArch=riscv64 __AlpinePackages="${__AlpinePackages// lldb-dev/}" __QEMUArch=riscv64 __UbuntuArch=riscv64 __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" unset __LLDB_Package ;; ppc64le) __BuildArch=ppc64le __AlpineArch=ppc64le __QEMUArch=ppc64le __UbuntuArch=ppc64el __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp5/}" unset __LLDB_Package ;; s390x) __BuildArch=s390x __AlpineArch=s390x __QEMUArch=s390x __UbuntuArch=s390x __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp5/}" unset __LLDB_Package ;; x64) __BuildArch=x64 __AlpineArch=x86_64 __UbuntuArch=amd64 __FreeBSDArch=amd64 __FreeBSDMachineArch=amd64 __OpenBSDArch=amd64 __OpenBSDMachineArch=amd64 __illumosArch=x86_64 __HaikuArch=x86_64 __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" ;; x86) __BuildArch=x86 __UbuntuArch=i386 __AlpineArch=x86 __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" ;; lldb*) version="$(echo "$lowerI" | tr -d '[:alpha:]-=')" majorVersion="${version%%.*}" [ -z "${version##*.*}" ] && minorVersion="${version#*.}" if [ -z "$minorVersion" ]; then minorVersion=0 fi # for versions > 6.0, lldb has dropped the minor version if [ "$majorVersion" -le 6 ]; then version="$majorVersion.$minorVersion" else version="$majorVersion" fi __LLDB_Package="liblldb-${version}-dev" ;; no-lldb) unset __LLDB_Package ;; llvm*) version="$(echo "$lowerI" | tr -d '[:alpha:]-=')" __LLVM_MajorVersion="${version%%.*}" [ -z "${version##*.*}" ] && __LLVM_MinorVersion="${version#*.}" if [ -z "$__LLVM_MinorVersion" ]; then __LLVM_MinorVersion=0 fi # for versions > 6.0, lldb has dropped the minor version if [ "$__LLVM_MajorVersion" -gt 6 ]; then __LLVM_MinorVersion= fi ;; xenial) # Ubuntu 16.04 __CodeName=xenial ;; bionic) # Ubuntu 18.04 __CodeName=bionic ;; focal) # Ubuntu 20.04 __CodeName=focal ;; jammy) # Ubuntu 22.04 __CodeName=jammy ;; noble) # Ubuntu 24.04 __CodeName=noble __LLDB_Package="liblldb-19-dev" ;; stretch) # Debian 9 __CodeName=stretch __LLDB_Package="liblldb-6.0-dev" __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.debian.org/debian/" fi ;; buster) # Debian 10 __CodeName=buster __LLDB_Package="liblldb-6.0-dev" __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://archive.debian.org/debian/" fi ;; bullseye) # Debian 11 __CodeName=bullseye __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.debian.org/debian/" fi ;; bookworm) # Debian 12 __CodeName=bookworm __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.debian.org/debian/" fi ;; sid) # Debian sid __CodeName=sid __UbuntuSuites= # Debian-Ports architectures need different values case "$__UbuntuArch" in amd64|arm64|armel|armhf|i386|mips64el|ppc64el|riscv64|s390x) __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.debian.org/debian/" fi ;; *) __KeyringFile="/usr/share/keyrings/debian-ports-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/" fi ;; esac if [[ -e "$__KeyringFile" ]]; then __Keyring="--keyring $__KeyringFile" fi ;; tizen) __CodeName= __UbuntuRepo= __Tizen=tizen ;; alpine*) __CodeName=alpine __UbuntuRepo= if [[ "$lowerI" == "alpineedge" ]]; then __AlpineVersion=edge else version="$(echo "$lowerI" | tr -d '[:alpha:]-=')" __AlpineMajorVersion="${version%%.*}" __AlpineMinorVersion="${version#*.}" __AlpineVersion="$__AlpineMajorVersion.$__AlpineMinorVersion" fi ;; freebsd13) __CodeName=freebsd __SkipUnmount=1 ;; freebsd14) __CodeName=freebsd __FreeBSDBase="14.3-RELEASE" __FreeBSDABI="14" __SkipUnmount=1 ;; openbsd) __CodeName=openbsd __SkipUnmount=1 ;; illumos) __CodeName=illumos __SkipUnmount=1 ;; haiku) __CodeName=haiku __SkipUnmount=1 ;; --skipunmount) __SkipUnmount=1 ;; --skipsigcheck) __SkipSigCheck=1 ;; --skipemulation) __SkipEmulation=1 ;; --rootfsdir|-rootfsdir) shift __RootfsDir="$1" ;; --use-mirror) __UseMirror=1 ;; --use-jobs) shift MAXJOBS=$1 ;; *) __UnprocessedBuildArgs="$__UnprocessedBuildArgs $1" ;; esac shift done case "$__AlpineVersion" in 3.14) __AlpinePackages+=" llvm11-libs" ;; 3.15) __AlpinePackages+=" llvm12-libs" ;; 3.16) __AlpinePackages+=" llvm13-libs" ;; 3.17) __AlpinePackages+=" llvm15-libs" ;; edge) __AlpineLlvmLibsLookup=1 ;; *) if [[ "$__AlpineArch" =~ s390x|ppc64le ]]; then __AlpineVersion=3.15 # minimum version that supports lldb-dev __AlpinePackages+=" llvm12-libs" elif [[ "$__AlpineArch" == "x86" ]]; then __AlpineVersion=3.17 # minimum version that supports lldb-dev __AlpinePackages+=" llvm15-libs" elif [[ "$__AlpineArch" == "riscv64" || "$__AlpineArch" == "loongarch64" ]]; then __AlpineVersion=3.21 # minimum version that supports lldb-dev __AlpinePackages+=" llvm19-libs" elif [[ -n "$__AlpineMajorVersion" ]]; then # use whichever alpine version is provided and select the latest toolchain libs __AlpineLlvmLibsLookup=1 else __AlpineVersion=3.13 # 3.13 to maximize compatibility __AlpinePackages+=" llvm10-libs" fi esac if [[ "$__AlpineVersion" =~ 3\.1[345] ]]; then # compiler-rt--static was merged in compiler-rt package in alpine 3.16 # for older versions, we need compiler-rt--static, so replace the name __AlpinePackages="${__AlpinePackages/compiler-rt/compiler-rt-static}" fi __UbuntuPackages+=" ${__LLDB_Package:-}" if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="http://ports.ubuntu.com/" fi if [[ -n "$__LLVM_MajorVersion" ]]; then __UbuntuPackages+=" libclang-common-${__LLVM_MajorVersion}${__LLVM_MinorVersion:+.$__LLVM_MinorVersion}-dev" fi if [[ -z "$__RootfsDir" && -n "$ROOTFS_DIR" ]]; then __RootfsDir="$ROOTFS_DIR" fi if [[ -z "$__RootfsDir" ]]; then __RootfsDir="$__CrossDir/../../../.tools/rootfs/$__BuildArch" fi if [[ -d "$__RootfsDir" ]]; then if [[ "$__SkipUnmount" == "0" ]]; then umount "$__RootfsDir"/* || true fi rm -rf "$__RootfsDir" fi mkdir -p "$__RootfsDir" __RootfsDir="$( cd "$__RootfsDir" && pwd )" __hasWget= ensureDownloadTool() { if command -v wget &> /dev/null; then __hasWget=1 elif command -v curl &> /dev/null; then __hasWget=0 else >&2 echo "ERROR: either wget or curl is required by this script." exit 1 fi } if [[ "$__CodeName" == "alpine" ]]; then __ApkToolsVersion=2.12.11 __ApkToolsDir="$(mktemp -d)" __ApkKeysDir="$(mktemp -d)" arch="$(uname -m)" ensureDownloadTool if [[ "$__hasWget" == 1 ]]; then wget -P "$__ApkToolsDir" "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v$__ApkToolsVersion/$arch/apk.static" else curl -SLO --create-dirs --output-dir "$__ApkToolsDir" "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v$__ApkToolsVersion/$arch/apk.static" fi if [[ "$arch" == "x86_64" ]]; then __ApkToolsSHA512SUM="53e57b49230da07ef44ee0765b9592580308c407a8d4da7125550957bb72cb59638e04f8892a18b584451c8d841d1c7cb0f0ab680cc323a3015776affaa3be33" elif [[ "$arch" == "aarch64" ]]; then __ApkToolsSHA512SUM="9e2b37ecb2b56c05dad23d379be84fd494c14bd730b620d0d576bda760588e1f2f59a7fcb2f2080577e0085f23a0ca8eadd993b4e61c2ab29549fdb71969afd0" else echo "WARNING: add missing hash for your host architecture. To find the value, use: 'find /tmp -name apk.static -exec sha512sum {} \;'" fi echo "$__ApkToolsSHA512SUM $__ApkToolsDir/apk.static" | sha512sum -c chmod +x "$__ApkToolsDir/apk.static" if [[ "$__AlpineVersion" == "edge" ]]; then version=edge else version="v$__AlpineVersion" fi for line in $__AlpineKeys; do id="${line%%:*}" content="${line#*:}" echo -e "-----BEGIN PUBLIC KEY-----\n$content\n-----END PUBLIC KEY-----" > "$__ApkKeysDir/alpine-devel@lists.alpinelinux.org-$id.rsa.pub" done if [[ "$__SkipSigCheck" == "1" ]]; then __ApkSignatureArg="--allow-untrusted" else __ApkSignatureArg="--keys-dir $__ApkKeysDir" fi if [[ "$__SkipEmulation" == "1" ]]; then __NoEmulationArg="--no-scripts" fi # initialize DB # shellcheck disable=SC2086 "$__ApkToolsDir/apk.static" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" --initdb add if [[ "$__AlpineLlvmLibsLookup" == 1 ]]; then # shellcheck disable=SC2086 __AlpinePackages+=" $("$__ApkToolsDir/apk.static" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \ search 'llvm*-libs' | grep -E '^llvm' | sort | tail -1 | sed 's/-[^-]*//2g')" fi # install all packages in one go # shellcheck disable=SC2086 "$__ApkToolsDir/apk.static" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" $__NoEmulationArg \ add $__AlpinePackages rm -r "$__ApkToolsDir" elif [[ "$__CodeName" == "freebsd" ]]; then mkdir -p "$__RootfsDir"/usr/local/etc JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} ensureDownloadTool if [[ "$__hasWget" == 1 ]]; then wget -O- "https://download.freebsd.org/ftp/releases/${__FreeBSDArch}/${__FreeBSDMachineArch}/${__FreeBSDBase}/base.txz" | tar -C "$__RootfsDir" -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version else curl -SL "https://download.freebsd.org/ftp/releases/${__FreeBSDArch}/${__FreeBSDMachineArch}/${__FreeBSDBase}/base.txz" | tar -C "$__RootfsDir" -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version fi echo "ABI = \"FreeBSD:${__FreeBSDABI}:${__FreeBSDMachineArch}\"; FINGERPRINTS = \"${__RootfsDir}/usr/share/keys\"; REPOS_DIR = [\"${__RootfsDir}/etc/pkg\"]; REPO_AUTOUPDATE = NO; RUN_SCRIPTS = NO;" > "${__RootfsDir}"/usr/local/etc/pkg.conf echo "FreeBSD: { url: \"pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf mkdir -p "$__RootfsDir"/tmp # get and build package manager if [[ "$__hasWget" == 1 ]]; then wget -O- "https://github.com/freebsd/pkg/archive/${__FreeBSDPkg}.tar.gz" | tar -C "$__RootfsDir"/tmp -zxf - else curl -SL "https://github.com/freebsd/pkg/archive/${__FreeBSDPkg}.tar.gz" | tar -C "$__RootfsDir"/tmp -zxf - fi cd "$__RootfsDir/tmp/pkg-${__FreeBSDPkg}" # needed for install to succeed mkdir -p "$__RootfsDir"/host/etc ./autogen.sh && ./configure --prefix="$__RootfsDir"/host && make -j "$JOBS" && make install rm -rf "$__RootfsDir/tmp/pkg-${__FreeBSDPkg}" # install packages we need. INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update # shellcheck disable=SC2086 INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf install --yes $__FreeBSDPackages elif [[ "$__CodeName" == "openbsd" ]]; then # determine mirrors OPENBSD_MIRROR="https://cdn.openbsd.org/pub/OpenBSD/$__OpenBSDVersion/$__OpenBSDMachineArch" # download base system sets ensureDownloadTool BASE_SETS=(base comp) for set in "${BASE_SETS[@]}"; do FILE="${set}${__OpenBSDVersion//./}.tgz" echo "Downloading $FILE..." if [[ "$__hasWget" == 1 ]]; then wget -O- "$OPENBSD_MIRROR/$FILE" | tar -C "$__RootfsDir" -xzpf - else curl -SL "$OPENBSD_MIRROR/$FILE" | tar -C "$__RootfsDir" -xzpf - fi done PKG_MIRROR="https://cdn.openbsd.org/pub/OpenBSD/${__OpenBSDVersion}/packages/${__OpenBSDMachineArch}" echo "Installing packages into sysroot..." # Fetch package index once if [[ "$__hasWget" == 1 ]]; then PKG_INDEX=$(wget -qO- "$PKG_MIRROR/") else PKG_INDEX=$(curl -s "$PKG_MIRROR/") fi for pkg in $__OpenBSDPackages; do PKG_FILE=$(echo "$PKG_INDEX" | grep -Po ">\K${pkg}-[0-9][^\" ]*\.tgz" \ | sort -V | tail -n1) echo "Resolved package filename for $pkg: $PKG_FILE" [[ -z "$PKG_FILE" ]] && { echo "ERROR: Package $pkg not found"; exit 1; } if [[ "$__hasWget" == 1 ]]; then wget -O- "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir" -xzpf - else curl -SL "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir" -xzpf - fi done echo "Creating versionless symlinks for shared libraries..." # Find all versioned .so files and create the base .so symlink for lib in "$__RootfsDir/usr/lib/libc++.so."* "$__RootfsDir/usr/lib/libc++abi.so."* "$__RootfsDir/usr/lib/libpthread.so."*; do if [ -f "$lib" ]; then # Extract the filename (e.g., libc++.so.12.0) VERSIONED_NAME=$(basename "$lib") # Remove the trailing version numbers (e.g., libc++.so) BASE_NAME=${VERSIONED_NAME%.so.*}.so # Create the symlink in the same directory ln -sf "$VERSIONED_NAME" "$__RootfsDir/usr/lib/$BASE_NAME" fi done elif [[ "$__CodeName" == "illumos" ]]; then mkdir "$__RootfsDir/tmp" pushd "$__RootfsDir/tmp" JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} ensureDownloadTool echo "Downloading sysroot." if [[ "$__hasWget" == 1 ]]; then wget -O- https://github.com/illumos/sysroot/releases/download/20181213-de6af22ae73b-v1/illumos-sysroot-i386-20181213-de6af22ae73b-v1.tar.gz | tar -C "$__RootfsDir" -xzf - else curl -SL https://github.com/illumos/sysroot/releases/download/20181213-de6af22ae73b-v1/illumos-sysroot-i386-20181213-de6af22ae73b-v1.tar.gz | tar -C "$__RootfsDir" -xzf - fi echo "Building binutils. Please wait.." if [[ "$__hasWget" == 1 ]]; then wget -O- https://ftp.gnu.org/gnu/binutils/binutils-2.42.tar.xz | tar -xJf - else curl -SL https://ftp.gnu.org/gnu/binutils/binutils-2.42.tar.xz | tar -xJf - fi mkdir build-binutils && cd build-binutils ../binutils-2.42/configure --prefix="$__RootfsDir" --target="${__illumosArch}-sun-solaris2.11" --program-prefix="${__illumosArch}-illumos-" --with-sysroot="$__RootfsDir" make -j "$JOBS" && make install && cd .. echo "Building gcc. Please wait.." if [[ "$__hasWget" == 1 ]]; then wget -O- https://ftp.gnu.org/gnu/gcc/gcc-13.3.0/gcc-13.3.0.tar.xz | tar -xJf - else curl -SL https://ftp.gnu.org/gnu/gcc/gcc-13.3.0/gcc-13.3.0.tar.xz | tar -xJf - fi CFLAGS="-fPIC" CXXFLAGS="-fPIC" CXXFLAGS_FOR_TARGET="-fPIC" CFLAGS_FOR_TARGET="-fPIC" export CFLAGS CXXFLAGS CXXFLAGS_FOR_TARGET CFLAGS_FOR_TARGET mkdir build-gcc && cd build-gcc ../gcc-13.3.0/configure --prefix="$__RootfsDir" --target="${__illumosArch}-sun-solaris2.11" --program-prefix="${__illumosArch}-illumos-" --with-sysroot="$__RootfsDir" --with-gnu-as \ --with-gnu-ld --disable-nls --disable-libgomp --disable-libquadmath --disable-libssp --disable-libvtv --disable-libcilkrts --disable-libada --disable-libsanitizer \ --disable-libquadmath-support --disable-shared --enable-tls make -j "$JOBS" && make install && cd .. BaseUrl=https://pkgsrc.smartos.org if [[ "$__UseMirror" == 1 ]]; then BaseUrl=https://pkgsrc.smartos.skylime.net fi BaseUrl="$BaseUrl/packages/SmartOS/2019Q4/${__illumosArch}/All" echo "Downloading manifest" if [[ "$__hasWget" == 1 ]]; then wget "$BaseUrl" else curl -SLO "$BaseUrl" fi echo "Downloading dependencies." read -ra array <<<"$__IllumosPackages" for package in "${array[@]}"; do echo "Installing '$package'" # find last occurrence of package in listing and extract its name package="$(sed -En '/.*href="('"$package"'-[0-9].*).tgz".*/h;$!d;g;s//\1/p' All)" echo "Resolved name '$package'" if [[ "$__hasWget" == 1 ]]; then wget "$BaseUrl"/"$package".tgz else curl -SLO "$BaseUrl"/"$package".tgz fi ar -x "$package".tgz tar --skip-old-files -xzf "$package".tmp.tg* -C "$__RootfsDir" 2>/dev/null done echo "Cleaning up temporary files." popd rm -rf "$__RootfsDir"/{tmp,+*} mkdir -p "$__RootfsDir"/usr/include/net mkdir -p "$__RootfsDir"/usr/include/netpacket if [[ "$__hasWget" == 1 ]]; then wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/bpf.h wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/dlt.h wget -P "$__RootfsDir"/usr/include/netpacket https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/inet/sockmods/netpacket/packet.h wget -P "$__RootfsDir"/usr/include/sys https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/sys/sdt.h else curl -SLO --create-dirs --output-dir "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/bpf.h curl -SLO --create-dirs --output-dir "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/dlt.h curl -SLO --create-dirs --output-dir "$__RootfsDir"/usr/include/netpacket https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/inet/sockmods/netpacket/packet.h curl -SLO --create-dirs --output-dir "$__RootfsDir"/usr/include/sys https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/sys/sdt.h fi elif [[ "$__CodeName" == "haiku" ]]; then JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} echo "Building Haiku sysroot for $__HaikuArch" mkdir -p "$__RootfsDir/tmp" pushd "$__RootfsDir/tmp" mkdir "$__RootfsDir/tmp/download" ensureDownloadTool echo "Downloading Haiku package tools" git clone https://github.com/haiku/haiku-toolchains-ubuntu --depth 1 "$__RootfsDir/tmp/script" if [[ "$__hasWget" == 1 ]]; then wget -O "$__RootfsDir/tmp/download/hosttools.zip" "$("$__RootfsDir/tmp/script/fetch.sh" --hosttools)" else curl -SLo "$__RootfsDir/tmp/download/hosttools.zip" "$("$__RootfsDir/tmp/script/fetch.sh" --hosttools)" fi unzip -o "$__RootfsDir/tmp/download/hosttools.zip" -d "$__RootfsDir/tmp/bin" HaikuBaseUrl="https://eu.hpkg.haiku-os.org/haiku/master/$__HaikuArch/current" HaikuPortsBaseUrl="https://eu.hpkg.haiku-os.org/haikuports/master/$__HaikuArch/current" echo "Downloading HaikuPorts package repository index..." if [[ "$__hasWget" == 1 ]]; then wget -P "$__RootfsDir/tmp/download" "$HaikuPortsBaseUrl/repo" else curl -SLO --create-dirs --output-dir "$__RootfsDir/tmp/download" "$HaikuPortsBaseUrl/repo" fi echo "Downloading Haiku packages" read -ra array <<<"$__HaikuPackages" for package in "${array[@]}"; do echo "Downloading $package..." hpkgFilename="$(LD_LIBRARY_PATH="$__RootfsDir/tmp/bin" "$__RootfsDir/tmp/bin/package_repo" list -f "$__RootfsDir/tmp/download/repo" | grep -E "${package}-" | sort -V | tail -n 1 | xargs)" if [ -z "$hpkgFilename" ]; then >&2 echo "ERROR: package $package missing." exit 1 fi echo "Resolved filename: $hpkgFilename..." hpkgDownloadUrl="$HaikuPortsBaseUrl/packages/$hpkgFilename" if [[ "$__hasWget" == 1 ]]; then wget -P "$__RootfsDir/tmp/download" "$hpkgDownloadUrl" else curl -SLO --create-dirs --output-dir "$__RootfsDir/tmp/download" "$hpkgDownloadUrl" fi done for package in haiku haiku_devel; do echo "Downloading $package..." if [[ "$__hasWget" == 1 ]]; then hpkgVersion="$(wget -qO- "$HaikuBaseUrl" | sed -n 's/^.*version: "\([^"]*\)".*$/\1/p')" wget -P "$__RootfsDir/tmp/download" "$HaikuBaseUrl/packages/$package-$hpkgVersion-1-$__HaikuArch.hpkg" else hpkgVersion="$(curl -sSL "$HaikuBaseUrl" | sed -n 's/^.*version: "\([^"]*\)".*$/\1/p')" curl -SLO --create-dirs --output-dir "$__RootfsDir/tmp/download" "$HaikuBaseUrl/packages/$package-$hpkgVersion-1-$__HaikuArch.hpkg" fi done # Set up the sysroot echo "Setting up sysroot and extracting required packages" mkdir -p "$__RootfsDir/boot/system" for file in "$__RootfsDir/tmp/download/"*.hpkg; do echo "Extracting $file..." LD_LIBRARY_PATH="$__RootfsDir/tmp/bin" "$__RootfsDir/tmp/bin/package" extract -C "$__RootfsDir/boot/system" "$file" done # Download buildtools echo "Downloading Haiku buildtools" if [[ "$__hasWget" == 1 ]]; then wget -O "$__RootfsDir/tmp/download/buildtools.zip" "$("$__RootfsDir/tmp/script/fetch.sh" --buildtools --arch=$__HaikuArch)" else curl -SLo "$__RootfsDir/tmp/download/buildtools.zip" "$("$__RootfsDir/tmp/script/fetch.sh" --buildtools --arch=$__HaikuArch)" fi unzip -o "$__RootfsDir/tmp/download/buildtools.zip" -d "$__RootfsDir" # Cleaning up temporary files echo "Cleaning up temporary files" popd rm -rf "$__RootfsDir/tmp" elif [[ -n "$__CodeName" ]]; then __Suites="$__CodeName $(for suite in $__UbuntuSuites; do echo -n "$__CodeName-$suite "; done)" if [[ "$__SkipEmulation" == "1" ]]; then if [[ -z "$AR" ]]; then if command -v ar &>/dev/null; then AR="$(command -v ar)" elif command -v llvm-ar &>/dev/null; then AR="$(command -v llvm-ar)" else echo "Unable to find ar or llvm-ar on PATH, add them to PATH or set AR environment variable pointing to the available AR tool" exit 1 fi fi PYTHON=${PYTHON_EXECUTABLE:-python3} # shellcheck disable=SC2086,SC2046 echo running "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ $(for suite in $__Suites; do echo -n "--suite $suite "; done) \ $__UbuntuPackages # shellcheck disable=SC2086,SC2046 "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ $(for suite in $__Suites; do echo -n "--suite $suite "; done) \ $__UbuntuPackages exit 0 fi __UpdateOptions= if [[ "$__SkipSigCheck" == "0" ]]; then __Keyring="$__Keyring --force-check-gpg" else __Keyring= __UpdateOptions="--allow-unauthenticated --allow-insecure-repositories" fi # shellcheck disable=SC2086 echo running debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo" # shellcheck disable=SC2086 if ! debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo"; then echo "debootstrap failed! dumping debootstrap.log" cat "$__RootfsDir/debootstrap/debootstrap.log" exit 1 fi rm -rf "$__RootfsDir"/etc/apt/*.{sources,list} "$__RootfsDir"/etc/apt/sources.list.d mkdir -p "$__RootfsDir/etc/apt/sources.list.d/" # shellcheck disable=SC2086 cat > "$__RootfsDir/etc/apt/sources.list.d/$__CodeName.sources" < token2) - (token1 < token2) else: return -1 if isinstance(token1, str) else 1 return len(tokens1) - len(tokens2) def compare_debian_versions(version1, version2): """Compare two Debian package versions.""" epoch1, upstream1, revision1 = parse_debian_version(version1) epoch2, upstream2, revision2 = parse_debian_version(version2) if epoch1 != epoch2: return epoch1 - epoch2 result = compare_upstream_version(upstream1, upstream2) if result != 0: return result return compare_upstream_version(revision1, revision2) def resolve_dependencies(packages, aliases, desired_packages): """Recursively resolves dependencies for the desired packages.""" resolved = [] to_process = deque(desired_packages) while to_process: current = to_process.popleft() resolved_package = current if current in packages else aliases.get(current, [None])[0] if not resolved_package: print(f"Error: Package '{current}' was not found in the available packages.") sys.exit(1) if resolved_package not in resolved: resolved.append(resolved_package) deps = packages.get(resolved_package, {}).get("Depends", "") if deps: deps = [dep.split(' ')[0] for dep in deps.split(', ') if dep] for dep in deps: if dep not in resolved and dep not in to_process and dep in packages: to_process.append(dep) return resolved def parse_package_index(content): """Parses the Packages.gz file and returns package information.""" packages = {} aliases = {} entries = re.split(r'\n\n+', content) for entry in entries: fields = dict(re.findall(r'^(\S+): (.+)$', entry, re.MULTILINE)) if "Package" in fields: package_name = fields["Package"] version = fields.get("Version") filename = fields.get("Filename") depends = fields.get("Depends") provides = fields.get("Provides", None) # Only update if package_name is not in packages or if the new version is higher if package_name not in packages or compare_debian_versions(version, packages[package_name]["Version"]) > 0: packages[package_name] = { "Version": version, "Filename": filename, "Depends": depends } # Update aliases if package provides any alternatives if provides: provides_list = [x.strip() for x in provides.split(",")] for alias in provides_list: # Strip version specifiers alias_name = re.sub(r'\s*\(=.*\)', '', alias) if alias_name not in aliases: aliases[alias_name] = [] if package_name not in aliases[alias_name]: aliases[alias_name].append(package_name) return packages, aliases def install_packages(mirror, packages_info, aliases, tmp_dir, extract_dir, ar_tool, desired_packages): """Downloads .deb files and extracts them.""" resolved_packages = resolve_dependencies(packages_info, aliases, desired_packages) print(f"Resolved packages (including dependencies): {resolved_packages}") packages_to_download = {} for pkg in resolved_packages: if pkg in packages_info: packages_to_download[pkg] = packages_info[pkg] if pkg in aliases: for alias in aliases[pkg]: if alias in packages_info: packages_to_download[alias] = packages_info[alias] asyncio.run(download_deb_files_parallel(mirror, packages_to_download, tmp_dir)) package_to_deb_file_map = {} for pkg in resolved_packages: pkg_info = packages_info.get(pkg) if pkg_info: deb_filename = pkg_info.get("Filename") if deb_filename: deb_file_path = os.path.join(tmp_dir, os.path.basename(deb_filename)) package_to_deb_file_map[pkg] = deb_file_path for pkg in reversed(resolved_packages): deb_file = package_to_deb_file_map.get(pkg) if deb_file and os.path.exists(deb_file): extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool) print("All done!") def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool): """Extract .deb file contents""" os.makedirs(extract_dir, exist_ok=True) with tempfile.TemporaryDirectory(dir=tmp_dir) as tmp_subdir: result = subprocess.run(f"{ar_tool} t {os.path.abspath(deb_file)}", cwd=tmp_subdir, check=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) tar_filename = None for line in result.stdout.decode().splitlines(): if line.startswith("data.tar"): tar_filename = line.strip() break if not tar_filename: raise FileNotFoundError(f"Could not find 'data.tar.*' in {deb_file}.") tar_file_path = os.path.join(tmp_subdir, tar_filename) print(f"Extracting {tar_filename} from {deb_file}..") subprocess.run(f"{ar_tool} p {os.path.abspath(deb_file)} {tar_filename} > {tar_file_path}", check=True, shell=True) file_extension = os.path.splitext(tar_file_path)[1].lower() if file_extension == ".xz": mode = "r:xz" elif file_extension == ".gz": mode = "r:gz" elif file_extension == ".zst": # zstd is not supported by standard library yet decompressed_tar_path = tar_file_path.replace(".zst", "") with open(tar_file_path, "rb") as zst_file, open(decompressed_tar_path, "wb") as decompressed_file: dctx = zstandard.ZstdDecompressor() dctx.copy_stream(zst_file, decompressed_file) tar_file_path = decompressed_tar_path mode = "r" else: raise ValueError(f"Unsupported compression format: {file_extension}") with tarfile.open(tar_file_path, mode) as tar: tar.extractall(path=extract_dir, filter='fully_trusted') def finalize_setup(rootfsdir): lib_dir = os.path.join(rootfsdir, 'lib') usr_lib_dir = os.path.join(rootfsdir, 'usr', 'lib') if os.path.exists(lib_dir): if os.path.islink(lib_dir): os.remove(lib_dir) else: os.makedirs(usr_lib_dir, exist_ok=True) for item in os.listdir(lib_dir): src = os.path.join(lib_dir, item) dest = os.path.join(usr_lib_dir, item) if os.path.isdir(src): shutil.copytree(src, dest, dirs_exist_ok=True) else: shutil.copy2(src, dest) shutil.rmtree(lib_dir) os.symlink(usr_lib_dir, lib_dir) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Generate rootfs for .NET runtime on Debian-like OS") parser.add_argument("--distro", required=False, help="Distro name (e.g., debian, ubuntu, etc.)") parser.add_argument("--arch", required=True, help="Architecture (e.g., amd64, loong64, etc.)") parser.add_argument("--rootfsdir", required=True, help="Destination directory.") parser.add_argument('--suite', required=True, action='append', help='Specify one or more repository suites to collect index data.') parser.add_argument("--mirror", required=False, help="Mirror (e.g., http://ftp.debian.org/debian-ports etc.)") parser.add_argument("--artool", required=False, default="ar", help="ar tool to extract debs (e.g., ar, llvm-ar etc.)") parser.add_argument("packages", nargs="+", help="List of package names to be installed.") args = parser.parse_args() if args.mirror is None: if args.distro == "ubuntu": args.mirror = "http://archive.ubuntu.com/ubuntu" if args.arch in ["amd64", "i386"] else "http://ports.ubuntu.com/ubuntu-ports" elif args.distro == "debian": args.mirror = "http://ftp.debian.org/debian-ports" else: raise Exception("Unsupported distro") DESIRED_PACKAGES = args.packages + [ # base packages "dpkg", "busybox", "libc-bin", "base-files", "base-passwd", "debianutils" ] print(f"Creating rootfs. rootfsdir: {args.rootfsdir}, distro: {args.distro}, arch: {args.arch}, suites: {args.suite}, mirror: {args.mirror}") package_index_content = asyncio.run(download_package_index_parallel(args.mirror, args.arch, args.suite)) packages_info, aliases = parse_package_index(package_index_content) with tempfile.TemporaryDirectory() as tmp_dir: install_packages(args.mirror, packages_info, aliases, tmp_dir, args.rootfsdir, args.artool, DESIRED_PACKAGES) finalize_setup(args.rootfsdir) ================================================ FILE: eng/common/cross/riscv64/tizen/tizen.patch ================================================ diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so --- a/usr/lib64/libc.so 2016-12-30 23:00:08.284951863 +0900 +++ b/usr/lib64/libc.so 2016-12-30 23:00:32.140951815 +0900 @@ -2,4 +2,4 @@ Use the shared library, but some functions are only in the static library, so try that secondarily. */ OUTPUT_FORMAT(elf64-littleriscv) -GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a AS_NEEDED ( /lib64/ld-linux-riscv64-lp64d.so.1 ) ) +GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-riscv64-lp64d.so.1 ) ) ================================================ FILE: eng/common/cross/tizen-build-rootfs.sh ================================================ #!/usr/bin/env bash set -e ARCH=$1 LINK_ARCH=$ARCH case "$ARCH" in arm) TIZEN_ARCH="armv7hl" ;; armel) TIZEN_ARCH="armv7l" LINK_ARCH="arm" ;; arm64) TIZEN_ARCH="aarch64" ;; x86) TIZEN_ARCH="i686" ;; x64) TIZEN_ARCH="x86_64" LINK_ARCH="x86" ;; riscv64) TIZEN_ARCH="riscv64" LINK_ARCH="riscv" ;; *) echo "Unsupported architecture for tizen: $ARCH" exit 1 esac __CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) __TIZEN_CROSSDIR="$__CrossDir/${ARCH}/tizen" if [[ -z "$ROOTFS_DIR" ]]; then echo "ROOTFS_DIR is not defined." exit 1; fi TIZEN_TMP_DIR=$ROOTFS_DIR/tizen_tmp mkdir -p $TIZEN_TMP_DIR # Download files echo ">>Start downloading files" VERBOSE=1 $__CrossDir/tizen-fetch.sh $TIZEN_TMP_DIR $TIZEN_ARCH echo "<>Start constructing Tizen rootfs" TIZEN_RPM_FILES=`ls $TIZEN_TMP_DIR/*.rpm` cd $ROOTFS_DIR for f in $TIZEN_RPM_FILES; do rpm2cpio $f | cpio -idm --quiet done echo "<>Start configuring Tizen rootfs" ln -sfn asm-${LINK_ARCH} ./usr/include/asm patch -p1 < $__TIZEN_CROSSDIR/tizen.patch if [[ "$TIZEN_ARCH" == "riscv64" ]]; then echo "Fixing broken symlinks in $PWD" rm ./usr/lib64/libresolv.so ln -s ../../lib64/libresolv.so.2 ./usr/lib64/libresolv.so rm ./usr/lib64/libpthread.so ln -s ../../lib64/libpthread.so.0 ./usr/lib64/libpthread.so rm ./usr/lib64/libdl.so ln -s ../../lib64/libdl.so.2 ./usr/lib64/libdl.so rm ./usr/lib64/libutil.so ln -s ../../lib64/libutil.so.1 ./usr/lib64/libutil.so rm ./usr/lib64/libm.so ln -s ../../lib64/libm.so.6 ./usr/lib64/libm.so rm ./usr/lib64/librt.so ln -s ../../lib64/librt.so.1 ./usr/lib64/librt.so rm ./lib/ld-linux-riscv64-lp64d.so.1 ln -s ../lib64/ld-linux-riscv64-lp64d.so.1 ./lib/ld-linux-riscv64-lp64d.so.1 fi echo "</dev/null; then VERBOSE=0 fi Log() { if [ $VERBOSE -ge 1 ]; then echo ${@:2} fi } Inform() { Log 1 -e "\x1B[0;34m$@\x1B[m" } Debug() { Log 2 -e "\x1B[0;32m$@\x1B[m" } Error() { >&2 Log 0 -e "\x1B[0;31m$@\x1B[m" } Fetch() { URL=$1 FILE=$2 PROGRESS=$3 if [ $VERBOSE -ge 1 ] && [ $PROGRESS ]; then CURL_OPT="--progress-bar" else CURL_OPT="--silent" fi curl $CURL_OPT $URL > $FILE } hash curl 2> /dev/null || { Error "Require 'curl' Aborting."; exit 1; } hash xmllint 2> /dev/null || { Error "Require 'xmllint' Aborting."; exit 1; } hash sha256sum 2> /dev/null || { Error "Require 'sha256sum' Aborting."; exit 1; } TMPDIR=$1 if [ ! -d $TMPDIR ]; then TMPDIR=./tizen_tmp Debug "Create temporary directory : $TMPDIR" mkdir -p $TMPDIR fi TIZEN_ARCH=$2 TIZEN_URL=http://download.tizen.org/snapshots/TIZEN/Tizen BUILD_XML=build.xml REPOMD_XML=repomd.xml PRIMARY_XML=primary.xml TARGET_URL="http://__not_initialized" Xpath_get() { XPATH_RESULT='' XPATH=$1 XML_FILE=$2 RESULT=$(xmllint --xpath $XPATH $XML_FILE) if [[ -z ${RESULT// } ]]; then Error "Can not find target from $XML_FILE" Debug "Xpath = $XPATH" exit 1 fi XPATH_RESULT=$RESULT } fetch_tizen_pkgs_init() { TARGET=$1 PROFILE=$2 Debug "Initialize TARGET=$TARGET, PROFILE=$PROFILE" TMP_PKG_DIR=$TMPDIR/tizen_${PROFILE}_pkgs if [ -d $TMP_PKG_DIR ]; then rm -rf $TMP_PKG_DIR; fi mkdir -p $TMP_PKG_DIR PKG_URL=$TIZEN_URL/$PROFILE/latest BUILD_XML_URL=$PKG_URL/$BUILD_XML TMP_BUILD=$TMP_PKG_DIR/$BUILD_XML TMP_REPOMD=$TMP_PKG_DIR/$REPOMD_XML TMP_PRIMARY=$TMP_PKG_DIR/$PRIMARY_XML TMP_PRIMARYGZ=${TMP_PRIMARY}.gz Fetch $BUILD_XML_URL $TMP_BUILD Debug "fetch $BUILD_XML_URL to $TMP_BUILD" TARGET_XPATH="//build/buildtargets/buildtarget[@name=\"$TARGET\"]/repo[@type=\"binary\"]/text()" Xpath_get $TARGET_XPATH $TMP_BUILD TARGET_PATH=$XPATH_RESULT TARGET_URL=$PKG_URL/$TARGET_PATH REPOMD_URL=$TARGET_URL/repodata/repomd.xml PRIMARY_XPATH='string(//*[local-name()="data"][@type="primary"]/*[local-name()="location"]/@href)' Fetch $REPOMD_URL $TMP_REPOMD Debug "fetch $REPOMD_URL to $TMP_REPOMD" Xpath_get $PRIMARY_XPATH $TMP_REPOMD PRIMARY_XML_PATH=$XPATH_RESULT PRIMARY_URL=$TARGET_URL/$PRIMARY_XML_PATH Fetch $PRIMARY_URL $TMP_PRIMARYGZ Debug "fetch $PRIMARY_URL to $TMP_PRIMARYGZ" gunzip $TMP_PRIMARYGZ Debug "unzip $TMP_PRIMARYGZ to $TMP_PRIMARY" } fetch_tizen_pkgs() { ARCH=$1 PACKAGE_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="location"]/@href)' PACKAGE_CHECKSUM_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="checksum"]/text())' for pkg in ${@:2} do Inform "Fetching... $pkg" XPATH=${PACKAGE_XPATH_TPL/_PKG_/$pkg} XPATH=${XPATH/_ARCH_/$ARCH} Xpath_get $XPATH $TMP_PRIMARY PKG_PATH=$XPATH_RESULT XPATH=${PACKAGE_CHECKSUM_XPATH_TPL/_PKG_/$pkg} XPATH=${XPATH/_ARCH_/$ARCH} Xpath_get $XPATH $TMP_PRIMARY CHECKSUM=$XPATH_RESULT PKG_URL=$TARGET_URL/$PKG_PATH PKG_FILE=$(basename $PKG_PATH) PKG_PATH=$TMPDIR/$PKG_FILE Debug "Download $PKG_URL to $PKG_PATH" Fetch $PKG_URL $PKG_PATH true echo "$CHECKSUM $PKG_PATH" | sha256sum -c - > /dev/null if [ $? -ne 0 ]; then Error "Fail to fetch $PKG_URL to $PKG_PATH" Debug "Checksum = $CHECKSUM" exit 1 fi done } BASE="Tizen-Base" UNIFIED="Tizen-Unified" Inform "Initialize ${TIZEN_ARCH} base" fetch_tizen_pkgs_init standard $BASE Inform "fetch common packages" fetch_tizen_pkgs ${TIZEN_ARCH} gcc gcc-devel-static glibc glibc-devel libicu libicu-devel libatomic linux-glibc-devel keyutils keyutils-devel libkeyutils Inform "fetch coreclr packages" fetch_tizen_pkgs ${TIZEN_ARCH} libgcc libstdc++ libstdc++-devel libunwind libunwind-devel lttng-ust-devel lttng-ust userspace-rcu-devel userspace-rcu if [ "$TIZEN_ARCH" != "riscv64" ]; then fetch_tizen_pkgs ${TIZEN_ARCH} lldb lldb-devel fi Inform "fetch corefx packages" fetch_tizen_pkgs ${TIZEN_ARCH} libcom_err libcom_err-devel zlib zlib-devel libopenssl11 libopenssl1.1-devel krb5 krb5-devel Inform "Initialize standard unified" fetch_tizen_pkgs_init standard $UNIFIED Inform "fetch corefx packages" fetch_tizen_pkgs ${TIZEN_ARCH} gssdp gssdp-devel tizen-release ================================================ FILE: eng/common/cross/toolchain.cmake ================================================ set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) # reset platform variables (e.g. cmake 3.25 sets LINUX=1) unset(LINUX) unset(FREEBSD) unset(OPENBSD) unset(ILLUMOS) unset(ANDROID) unset(TIZEN) unset(HAIKU) set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) file(GLOB OPENBSD_PROBE "${CROSS_ROOTFS}/etc/signify/openbsd-*.pub") if(EXISTS ${CROSS_ROOTFS}/bin/freebsd-version) set(CMAKE_SYSTEM_NAME FreeBSD) set(FREEBSD 1) elseif(OPENBSD_PROBE) set(CMAKE_SYSTEM_NAME OpenBSD) set(OPENBSD 1) elseif(EXISTS ${CROSS_ROOTFS}/usr/platform/i86pc) set(CMAKE_SYSTEM_NAME SunOS) set(ILLUMOS 1) elseif(EXISTS ${CROSS_ROOTFS}/boot/system/develop/headers/config/HaikuConfig.h) set(CMAKE_SYSTEM_NAME Haiku) set(HAIKU 1) else() set(CMAKE_SYSTEM_NAME Linux) set(LINUX 1) endif() set(CMAKE_SYSTEM_VERSION 1) if(EXISTS ${CROSS_ROOTFS}/etc/tizen-release) set(TIZEN 1) elseif(EXISTS ${CROSS_ROOTFS}/android_platform) set(ANDROID 1) endif() if(TARGET_ARCH_NAME STREQUAL "arm") set(CMAKE_SYSTEM_PROCESSOR armv7l) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv7-alpine-linux-musleabihf) set(TOOLCHAIN "armv7-alpine-linux-musleabihf") elseif(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) set(TOOLCHAIN "armv6-alpine-linux-musleabihf") else() set(TOOLCHAIN "arm-linux-gnueabihf") endif() if(TIZEN) set(TIZEN_TOOLCHAIN "armv7hl-tizen-linux-gnueabihf") endif() elseif(TARGET_ARCH_NAME STREQUAL "arm64") set(CMAKE_SYSTEM_PROCESSOR aarch64) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/aarch64-alpine-linux-musl) set(TOOLCHAIN "aarch64-alpine-linux-musl") elseif(LINUX) set(TOOLCHAIN "aarch64-linux-gnu") if(TIZEN) set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu") endif() elseif(FREEBSD) set(triple "aarch64-unknown-freebsd12") elseif(OPENBSD) set(triple "aarch64-unknown-openbsd") endif() elseif(TARGET_ARCH_NAME STREQUAL "armel") set(CMAKE_SYSTEM_PROCESSOR armv7l) set(TOOLCHAIN "arm-linux-gnueabi") if(TIZEN) set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi") endif() elseif(TARGET_ARCH_NAME STREQUAL "armv6") set(CMAKE_SYSTEM_PROCESSOR armv6l) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) set(TOOLCHAIN "armv6-alpine-linux-musleabihf") else() set(TOOLCHAIN "arm-linux-gnueabihf") endif() elseif(TARGET_ARCH_NAME STREQUAL "loongarch64") set(CMAKE_SYSTEM_PROCESSOR "loongarch64") if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/loongarch64-alpine-linux-musl) set(TOOLCHAIN "loongarch64-alpine-linux-musl") else() set(TOOLCHAIN "loongarch64-linux-gnu") endif() elseif(TARGET_ARCH_NAME STREQUAL "ppc64le") set(CMAKE_SYSTEM_PROCESSOR ppc64le) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/powerpc64le-alpine-linux-musl) set(TOOLCHAIN "powerpc64le-alpine-linux-musl") else() set(TOOLCHAIN "powerpc64le-linux-gnu") endif() elseif(TARGET_ARCH_NAME STREQUAL "riscv64") set(CMAKE_SYSTEM_PROCESSOR riscv64) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/riscv64-alpine-linux-musl) set(TOOLCHAIN "riscv64-alpine-linux-musl") else() set(TOOLCHAIN "riscv64-linux-gnu") if(TIZEN) set(TIZEN_TOOLCHAIN "riscv64-tizen-linux-gnu") endif() endif() elseif(TARGET_ARCH_NAME STREQUAL "s390x") set(CMAKE_SYSTEM_PROCESSOR s390x) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/s390x-alpine-linux-musl) set(TOOLCHAIN "s390x-alpine-linux-musl") else() set(TOOLCHAIN "s390x-linux-gnu") endif() elseif(TARGET_ARCH_NAME STREQUAL "x64") set(CMAKE_SYSTEM_PROCESSOR x86_64) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/x86_64-alpine-linux-musl) set(TOOLCHAIN "x86_64-alpine-linux-musl") elseif(LINUX) set(TOOLCHAIN "x86_64-linux-gnu") if(TIZEN) set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu") endif() elseif(FREEBSD) set(triple "x86_64-unknown-freebsd12") elseif(OPENBSD) set(triple "x86_64-unknown-openbsd") elseif(ILLUMOS) set(TOOLCHAIN "x86_64-illumos") elseif(HAIKU) set(TOOLCHAIN "x86_64-unknown-haiku") endif() elseif(TARGET_ARCH_NAME STREQUAL "x86") set(CMAKE_SYSTEM_PROCESSOR i686) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) set(TOOLCHAIN "i586-alpine-linux-musl") else() set(TOOLCHAIN "i686-linux-gnu") endif() if(TIZEN) set(TIZEN_TOOLCHAIN "i586-tizen-linux-gnu") endif() else() message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only arm, arm64, armel, armv6, loongarch64, ppc64le, riscv64, s390x, x64 and x86 are supported!") endif() if(DEFINED ENV{TOOLCHAIN}) set(TOOLCHAIN $ENV{TOOLCHAIN}) endif() # Specify include paths if(TIZEN) function(find_toolchain_dir prefix) # Dynamically find the version subdirectory file(GLOB DIRECTORIES "${prefix}/*") list(GET DIRECTORIES 0 FIRST_MATCH) get_filename_component(TOOLCHAIN_VERSION ${FIRST_MATCH} NAME) set(TIZEN_TOOLCHAIN_PATH "${prefix}/${TOOLCHAIN_VERSION}" PARENT_SCOPE) endfunction() if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") find_toolchain_dir("${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") else() find_toolchain_dir("${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") endif() message(STATUS "TIZEN_TOOLCHAIN_PATH set to: ${TIZEN_TOOLCHAIN_PATH}") include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++) include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++/${TIZEN_TOOLCHAIN}) endif() function(locate_toolchain_exec exec var) set(TOOLSET_PREFIX ${TOOLCHAIN}-) string(TOUPPER ${exec} EXEC_UPPERCASE) if(NOT "$ENV{CLR_${EXEC_UPPERCASE}}" STREQUAL "") set(${var} "$ENV{CLR_${EXEC_UPPERCASE}}" PARENT_SCOPE) return() endif() find_program(EXEC_LOCATION_${exec} NAMES "${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}" "${TOOLSET_PREFIX}${exec}") if (EXEC_LOCATION_${exec} STREQUAL "EXEC_LOCATION_${exec}-NOTFOUND") message(FATAL_ERROR "Unable to find toolchain executable. Name: ${exec}, Prefix: ${TOOLSET_PREFIX}.") endif() set(${var} ${EXEC_LOCATION_${exec}} PARENT_SCOPE) endfunction() if(ANDROID) if(TARGET_ARCH_NAME STREQUAL "arm") set(ANDROID_ABI armeabi-v7a) elseif(TARGET_ARCH_NAME STREQUAL "arm64") set(ANDROID_ABI arm64-v8a) endif() # extract platform number required by the NDK's toolchain file(READ "${CROSS_ROOTFS}/android_platform" RID_FILE_CONTENTS) string(REPLACE "RID=" "" ANDROID_RID "${RID_FILE_CONTENTS}") string(REGEX REPLACE ".*\\.([0-9]+)-.*" "\\1" ANDROID_PLATFORM "${ANDROID_RID}") set(ANDROID_TOOLCHAIN clang) set(FEATURE_EVENT_TRACE 0) # disable event trace as there is no lttng-ust package in termux repository set(CMAKE_SYSTEM_LIBRARY_PATH "${CROSS_ROOTFS}/usr/lib") set(CMAKE_SYSTEM_INCLUDE_PATH "${CROSS_ROOTFS}/usr/include") # include official NDK toolchain script include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake) elseif(FREEBSD OR OPENBSD) # we cross-compile by instructing clang set(CMAKE_C_COMPILER_TARGET ${triple}) set(CMAKE_CXX_COMPILER_TARGET ${triple}) set(CMAKE_ASM_COMPILER_TARGET ${triple}) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fuse-ld=lld") elseif(ILLUMOS) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") include_directories(SYSTEM ${CROSS_ROOTFS}/include) locate_toolchain_exec(gcc CMAKE_C_COMPILER) locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) elseif(HAIKU) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") if ($ENV{CCC_CC} MATCHES ".*gcc.*") set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin") locate_toolchain_exec(gcc CMAKE_C_COMPILER) locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) else() set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64") set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64") set(CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64") endif() # let CMake set up the correct search paths include(Platform/Haiku) else() set(CMAKE_SYSROOT "${CROSS_ROOTFS}") set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") set(CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") endif() # Specify link flags function(add_toolchain_linker_flag Flag) set(Config "${ARGV1}") set(CONFIG_SUFFIX "") if (NOT Config STREQUAL "") set(CONFIG_SUFFIX "_${Config}") endif() set("CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}_INIT" "${CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}_INIT} ${Flag}" PARENT_SCOPE) set("CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}_INIT" "${CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}_INIT} ${Flag}" PARENT_SCOPE) endfunction() if(LINUX) add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib/${TOOLCHAIN}") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}") endif() if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") if(TIZEN) add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") endif() elseif(TARGET_ARCH_NAME MATCHES "^(arm64|x64|riscv64)$") if(TIZEN) add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib64") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64") add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib64") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64") add_toolchain_linker_flag("-Wl,--rpath-link=${TIZEN_TOOLCHAIN_PATH}") endif() elseif(TARGET_ARCH_NAME STREQUAL "s390x") add_toolchain_linker_flag("--target=${TOOLCHAIN}") elseif(TARGET_ARCH_NAME STREQUAL "x86") if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) add_toolchain_linker_flag("--target=${TOOLCHAIN}") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}") endif() add_toolchain_linker_flag(-m32) if(TIZEN) add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") endif() elseif(ILLUMOS) add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib/amd64") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/amd64/lib") elseif(HAIKU) add_toolchain_linker_flag("-lnetwork") add_toolchain_linker_flag("-lroot") endif() # Specify compile options if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|loongarch64|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD AND NOT OPENBSD) OR ILLUMOS OR HAIKU) set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) endif() if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") add_compile_options(-mthumb) if (NOT DEFINED CLR_ARM_FPU_TYPE) set (CLR_ARM_FPU_TYPE vfpv3) endif (NOT DEFINED CLR_ARM_FPU_TYPE) add_compile_options (-mfpu=${CLR_ARM_FPU_TYPE}) if (NOT DEFINED CLR_ARM_FPU_CAPABILITY) set (CLR_ARM_FPU_CAPABILITY 0x7) endif (NOT DEFINED CLR_ARM_FPU_CAPABILITY) add_definitions (-DCLR_ARM_FPU_CAPABILITY=${CLR_ARM_FPU_CAPABILITY}) # persist variables across multiple try_compile passes list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES CLR_ARM_FPU_TYPE CLR_ARM_FPU_CAPABILITY) if(TARGET_ARCH_NAME STREQUAL "armel") add_compile_options(-mfloat-abi=softfp) endif() elseif(TARGET_ARCH_NAME STREQUAL "s390x") add_compile_options("--target=${TOOLCHAIN}") elseif(TARGET_ARCH_NAME STREQUAL "x86") if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) add_compile_options(--target=${TOOLCHAIN}) endif() add_compile_options(-m32) add_compile_options(-Wno-error=unused-command-line-argument) endif() if(TIZEN) if(TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64|x86)$") add_compile_options(-Wno-deprecated-declarations) # compile-time option add_compile_options(-D__extern_always_inline=inline) # compile-time option endif() endif() # Set LLDB include and library paths for builds that need lldb. if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") if(TARGET_ARCH_NAME STREQUAL "x86") set(LLVM_CROSS_DIR "$ENV{LLVM_CROSS_HOME}") else() # arm/armel case set(LLVM_CROSS_DIR "$ENV{LLVM_ARM_HOME}") endif() if(LLVM_CROSS_DIR) set(WITH_LLDB_LIBS "${LLVM_CROSS_DIR}/lib/" CACHE STRING "") set(WITH_LLDB_INCLUDES "${LLVM_CROSS_DIR}/include" CACHE STRING "") set(LLDB_H "${WITH_LLDB_INCLUDES}" CACHE STRING "") set(LLDB "${LLVM_CROSS_DIR}/lib/liblldb.so" CACHE STRING "") else() if(TARGET_ARCH_NAME STREQUAL "x86") set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/i386-linux-gnu" CACHE STRING "") set(CHECK_LLVM_DIR "${CROSS_ROOTFS}/usr/lib/llvm-3.8/include") if(EXISTS "${CHECK_LLVM_DIR}" AND IS_DIRECTORY "${CHECK_LLVM_DIR}") set(WITH_LLDB_INCLUDES "${CHECK_LLVM_DIR}") else() set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include") endif() else() # arm/armel case set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}" CACHE STRING "") set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include" CACHE STRING "") endif() endif() endif() # Set C++ standard library options if specified set(CLR_CMAKE_CXX_STANDARD_LIBRARY "" CACHE STRING "Standard library flavor to link against. Only supported with the Clang compiler.") if (CLR_CMAKE_CXX_STANDARD_LIBRARY) add_compile_options($<$:--stdlib=${CLR_CMAKE_CXX_STANDARD_LIBRARY}>) add_link_options($<$:--stdlib=${CLR_CMAKE_CXX_STANDARD_LIBRARY}>) endif() option(CLR_CMAKE_CXX_STANDARD_LIBRARY_STATIC "Statically link against the C++ standard library" OFF) if(CLR_CMAKE_CXX_STANDARD_LIBRARY_STATIC) add_link_options($<$:-static-libstdc++>) endif() set(CLR_CMAKE_CXX_ABI_LIBRARY "" CACHE STRING "C++ ABI implementation library to link against. Only supported with the Clang compiler.") if (CLR_CMAKE_CXX_ABI_LIBRARY) # The user may specify the ABI library with the 'lib' prefix, like 'libstdc++'. Strip the prefix here so the linker finds the right library. string(REGEX REPLACE "^lib(.+)" "\\1" CLR_CMAKE_CXX_ABI_LIBRARY ${CLR_CMAKE_CXX_ABI_LIBRARY}) # We need to specify this as a linker-backend option as Clang will filter this option out when linking to libc++. add_link_options("LINKER:-l${CLR_CMAKE_CXX_ABI_LIBRARY}") endif() set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) ================================================ FILE: eng/common/darc-init.ps1 ================================================ param ( $darcVersion = $null, $versionEndpoint = 'https://maestro.dot.net/api/assets/darc-version?api-version=2020-02-20', $verbosity = 'minimal', $toolpath = $null ) . $PSScriptRoot\tools.ps1 function InstallDarcCli ($darcVersion, $toolpath) { $darcCliPackageName = 'microsoft.dotnet.darc' $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" $toolList = & "$dotnet" tool list -g if ($toolList -like "*$darcCliPackageName*") { & "$dotnet" tool uninstall $darcCliPackageName -g } # If the user didn't explicitly specify the darc version, # query the Maestro API for the correct version of darc to install. if (-not $darcVersion) { $darcVersion = $(Invoke-WebRequest -Uri $versionEndpoint -UseBasicParsing).Content } $arcadeServicesSource = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' Write-Host "Installing Darc CLI version $darcVersion..." Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' if (-not $toolpath) { Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --source '$arcadeServicesSource' -v $verbosity -g" & "$dotnet" tool install $darcCliPackageName --version $darcVersion --source "$arcadeServicesSource" -v $verbosity -g }else { Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" & "$dotnet" tool install $darcCliPackageName --version $darcVersion --source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" } } try { InstallDarcCli $darcVersion $toolpath } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'Darc' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/darc-init.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" darcVersion='' versionEndpoint='https://maestro.dot.net/api/assets/darc-version?api-version=2020-02-20' verbosity='minimal' while [[ $# -gt 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --darcversion) darcVersion=$2 shift ;; --versionendpoint) versionEndpoint=$2 shift ;; --verbosity) verbosity=$2 shift ;; --toolpath) toolpath=$2 shift ;; *) echo "Invalid argument: $1" usage exit 1 ;; esac shift done # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . "$scriptroot/tools.sh" if [ -z "$darcVersion" ]; then darcVersion=$(curl -X GET "$versionEndpoint" -H "accept: text/plain") fi function InstallDarcCli { local darc_cli_package_name="microsoft.dotnet.darc" InitializeDotNetCli true local dotnet_root=$_InitializeDotNetCli if [ -z "$toolpath" ]; then local tool_list=$($dotnet_root/dotnet tool list -g) if [[ $tool_list = *$darc_cli_package_name* ]]; then echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name -g) fi else local tool_list=$($dotnet_root/dotnet tool list --tool-path "$toolpath") if [[ $tool_list = *$darc_cli_package_name* ]]; then echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name --tool-path "$toolpath") fi fi local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" echo "Installing Darc CLI version $darcVersion..." echo "You may need to restart your command shell if this is the first dotnet tool you have installed." if [ -z "$toolpath" ]; then echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --source "$arcadeServicesSource" -v $verbosity -g) else echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") fi } InstallDarcCli ================================================ FILE: eng/common/dotnet-install.cmd ================================================ @echo off powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0dotnet-install.ps1""" %*" ================================================ FILE: eng/common/dotnet-install.ps1 ================================================ [CmdletBinding(PositionalBinding=$false)] Param( [string] $verbosity = 'minimal', [string] $architecture = '', [string] $version = 'Latest', [string] $runtime = 'dotnet', [string] $RuntimeSourceFeed = '', [string] $RuntimeSourceFeedKey = '' ) . $PSScriptRoot\tools.ps1 if (-not [string]::IsNullOrEmpty($env:DOTNET_GLOBAL_INSTALL_DIR)) { $dotnetRoot = $env:DOTNET_GLOBAL_INSTALL_DIR } else { $dotnetRoot = Join-Path $RepoRoot '.dotnet' } $installdir = $dotnetRoot try { if ($architecture -and $architecture.Trim() -eq 'x86') { $installdir = Join-Path $installdir 'x86' } InstallDotNet $installdir $version $architecture $runtime $true -RuntimeSourceFeed $RuntimeSourceFeed -RuntimeSourceFeedKey $RuntimeSourceFeedKey } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } ExitWithExitCode 0 ================================================ FILE: eng/common/dotnet-install.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . "$scriptroot/tools.sh" version='Latest' architecture='' runtime='dotnet' runtimeSourceFeed='' runtimeSourceFeedKey='' while [[ $# -gt 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in -version|-v) shift version="$1" ;; -architecture|-a) shift architecture="$1" ;; -runtime|-r) shift runtime="$1" ;; -runtimesourcefeed) shift runtimeSourceFeed="$1" ;; -runtimesourcefeedkey) shift runtimeSourceFeedKey="$1" ;; *) Write-PipelineTelemetryError -Category 'Build' -Message "Invalid argument: $1" exit 1 ;; esac shift done # Use uname to determine what the CPU is, see https://en.wikipedia.org/wiki/Uname#Examples cpuname=$(uname -m) case $cpuname in arm64|aarch64) buildarch=arm64 if [ "$(getconf LONG_BIT)" -lt 64 ]; then # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS) buildarch=arm fi ;; loongarch64) buildarch=loongarch64 ;; amd64|x86_64) buildarch=x64 ;; armv*l) buildarch=arm ;; i[3-6]86) buildarch=x86 ;; riscv64) buildarch=riscv64 ;; *) echo "Unknown CPU $cpuname detected, treating it as x64" buildarch=x64 ;; esac if [[ -n "${DOTNET_GLOBAL_INSTALL_DIR:-}" ]]; then dotnetRoot="$DOTNET_GLOBAL_INSTALL_DIR" else dotnetRoot="${repo_root}.dotnet" fi if [[ $architecture != "" ]] && [[ $architecture != $buildarch ]]; then dotnetRoot="$dotnetRoot/$architecture" fi InstallDotNet "$dotnetRoot" $version "$architecture" $runtime true $runtimeSourceFeed $runtimeSourceFeedKey || { local exit_code=$? Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "dotnet-install.sh failed (exit code '$exit_code')." >&2 ExitWithExitCode $exit_code } ExitWithExitCode 0 ================================================ FILE: eng/common/dotnet.cmd ================================================ @echo off :: This script is used to install the .NET SDK. :: It will also invoke the SDK with any provided arguments. powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0dotnet.ps1""" %*" exit /b %ErrorLevel% ================================================ FILE: eng/common/dotnet.ps1 ================================================ # This script is used to install the .NET SDK. # It will also invoke the SDK with any provided arguments. . $PSScriptRoot\tools.ps1 $dotnetRoot = InitializeDotNetCli -install:$true # Invoke acquired SDK with args if they are provided if ($args.count -gt 0) { $env:DOTNET_NOLOGO=1 & "$dotnetRoot\dotnet.exe" $args } ================================================ FILE: eng/common/dotnet.sh ================================================ #!/usr/bin/env bash # This script is used to install the .NET SDK. # It will also invoke the SDK with any provided arguments. source="${BASH_SOURCE[0]}" # resolve $SOURCE until the file is no longer a symlink while [[ -h $source ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source $scriptroot/tools.sh InitializeDotNetCli true # install # Invoke acquired SDK with args if they are provided if [[ $# -gt 0 ]]; then __dotnetDir=${_InitializeDotNetCli} dotnetPath=${__dotnetDir}/dotnet ${dotnetPath} "$@" fi ================================================ FILE: eng/common/enable-cross-org-publishing.ps1 ================================================ param( [string] $token ) . $PSScriptRoot\pipeline-logging-functions.ps1 # Write-PipelineSetVariable will no-op if a variable named $ci is not defined # Since this script is only ever called in AzDO builds, just universally set it $ci = $true Write-PipelineSetVariable -Name 'VSS_NUGET_ACCESSTOKEN' -Value $token -IsMultiJobVariable $false Write-PipelineSetVariable -Name 'VSS_NUGET_URI_PREFIXES' -Value 'https://dnceng.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/dnceng/;https://devdiv.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/devdiv/' -IsMultiJobVariable $false ================================================ FILE: eng/common/generate-locproject.ps1 ================================================ Param( [Parameter(Mandatory=$true)][string] $SourcesDirectory, # Directory where source files live; if using a Localize directory it should live in here [string] $LanguageSet = 'VS_Main_Languages', # Language set to be used in the LocProject.json [switch] $UseCheckedInLocProjectJson, # When set, generates a LocProject.json and compares it to one that already exists in the repo; otherwise just generates one [switch] $CreateNeutralXlfs # Creates neutral xlf files. Only set to false when running locally ) # Generates LocProject.json files for the OneLocBuild task. OneLocBuildTask is described here: # https://ceapex.visualstudio.com/CEINTL/_wiki/wikis/CEINTL.wiki/107/Localization-with-OneLocBuild-Task Set-StrictMode -Version 2.0 $ErrorActionPreference = "Stop" . $PSScriptRoot\pipeline-logging-functions.ps1 $exclusionsFilePath = "$SourcesDirectory\eng\Localize\LocExclusions.json" $exclusions = @{ Exclusions = @() } if (Test-Path -Path $exclusionsFilePath) { $exclusions = Get-Content "$exclusionsFilePath" | ConvertFrom-Json } Push-Location "$SourcesDirectory" # push location for Resolve-Path -Relative to work # Template files $jsonFiles = @() $jsonTemplateFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\.template\.config\\localize\\.+\.en\.json" } # .NET templating pattern $jsonTemplateFiles | ForEach-Object { $null = $_.Name -Match "(.+)\.[\w-]+\.json" # matches '[filename].[langcode].json $destinationFile = "$($_.Directory.FullName)\$($Matches.1).json" $jsonFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru } $jsonWinformsTemplateFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\\strings\.json" } # current winforms pattern $wxlFilesV3 = @() $wxlFilesV5 = @() $wxlFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\.+\.wxl" -And -Not( $_.Directory.Name -Match "\d{4}" ) } # localized files live in four digit lang ID directories; this excludes them if (-not $wxlFiles) { $wxlEnFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\1033\\.+\.wxl" } # pick up en files (1033 = en) specifically so we can copy them to use as the neutral xlf files if ($wxlEnFiles) { $wxlFiles = @() $wxlEnFiles | ForEach-Object { $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" $content = Get-Content $_.FullName -Raw # Split files on schema to select different parser settings in the generated project. if ($content -like "*http://wixtoolset.org/schemas/v4/wxl*") { $wxlFilesV5 += Copy-Item $_.FullName -Destination $destinationFile -PassThru } elseif ($content -like "*http://schemas.microsoft.com/wix/2006/localization*") { $wxlFilesV3 += Copy-Item $_.FullName -Destination $destinationFile -PassThru } } } } $macosHtmlEnFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\.lproj\\.+\.html$" } # add installer HTML files $macosHtmlFiles = @() if ($macosHtmlEnFiles) { $macosHtmlEnFiles | ForEach-Object { $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" $macosHtmlFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru } } $xlfFiles = @() $allXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.xlf" $langXlfFiles = @() if ($allXlfFiles) { $null = $allXlfFiles[0].FullName -Match "\.([\w-]+)\.xlf" # matches '[langcode].xlf' $firstLangCode = $Matches.1 $langXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.$firstLangCode.xlf" } $langXlfFiles | ForEach-Object { $null = $_.Name -Match "(.+)\.[\w-]+\.xlf" # matches '[filename].[langcode].xlf $destinationFile = "$($_.Directory.FullName)\$($Matches.1).xlf" $xlfFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru } $locFiles = $jsonFiles + $jsonWinformsTemplateFiles + $xlfFiles $locJson = @{ Projects = @( @{ LanguageSet = $LanguageSet LocItems = @( $locFiles | ForEach-Object { $outputPath = "$(($_.DirectoryName | Resolve-Path -Relative) + "\")" $continue = $true foreach ($exclusion in $exclusions.Exclusions) { if ($_.FullName.Contains($exclusion)) { $continue = $false } } $sourceFile = ($_.FullName | Resolve-Path -Relative) if (!$CreateNeutralXlfs -and $_.Extension -eq '.xlf') { Remove-Item -Path $sourceFile } if ($continue) { if ($_.Directory.Name -eq 'en' -and $_.Extension -eq '.json') { return @{ SourceFile = $sourceFile CopyOption = "LangIDOnPath" OutputPath = "$($_.Directory.Parent.FullName | Resolve-Path -Relative)\" } } else { return @{ SourceFile = $sourceFile CopyOption = "LangIDOnName" OutputPath = $outputPath } } } } ) }, @{ LanguageSet = $LanguageSet CloneLanguageSet = "WiX_CloneLanguages" LssFiles = @( "wxl_loc.lss" ) LocItems = @( $wxlFilesV3 | ForEach-Object { $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" $continue = $true foreach ($exclusion in $exclusions.Exclusions) { if ($_.FullName.Contains($exclusion)) { $continue = $false } } $sourceFile = ($_.FullName | Resolve-Path -Relative) if ($continue) { return @{ SourceFile = $sourceFile CopyOption = "LangIDOnPath" OutputPath = $outputPath } } } ) }, @{ LanguageSet = $LanguageSet CloneLanguageSet = "WiX_CloneLanguages" LssFiles = @( "P210WxlSchemaV4.lss" ) LocItems = @( $wxlFilesV5 | ForEach-Object { $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" $continue = $true foreach ($exclusion in $exclusions.Exclusions) { if ($_.FullName.Contains($exclusion)) { $continue = $false } } $sourceFile = ($_.FullName | Resolve-Path -Relative) if ($continue) { return @{ SourceFile = $sourceFile CopyOption = "LangIDOnPath" OutputPath = $outputPath } } } ) }, @{ LanguageSet = $LanguageSet CloneLanguageSet = "VS_macOS_CloneLanguages" LssFiles = @( ".\eng\common\loc\P22DotNetHtmlLocalization.lss" ) LocItems = @( $macosHtmlFiles | ForEach-Object { $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" $continue = $true foreach ($exclusion in $exclusions.Exclusions) { if ($_.FullName.Contains($exclusion)) { $continue = $false } } $sourceFile = ($_.FullName | Resolve-Path -Relative) $lciFile = $sourceFile + ".lci" if ($continue) { $result = @{ SourceFile = $sourceFile CopyOption = "LangIDOnPath" OutputPath = $outputPath } if (Test-Path $lciFile -PathType Leaf) { $result["LciFile"] = $lciFile } return $result } } ) } ) } $json = ConvertTo-Json $locJson -Depth 5 Write-Host "LocProject.json generated:`n`n$json`n`n" Pop-Location if (!$UseCheckedInLocProjectJson) { New-Item "$SourcesDirectory\eng\Localize\LocProject.json" -Force # Need this to make sure the Localize directory is created Set-Content "$SourcesDirectory\eng\Localize\LocProject.json" $json } else { New-Item "$SourcesDirectory\eng\Localize\LocProject-generated.json" -Force # Need this to make sure the Localize directory is created Set-Content "$SourcesDirectory\eng\Localize\LocProject-generated.json" $json if ((Get-FileHash "$SourcesDirectory\eng\Localize\LocProject-generated.json").Hash -ne (Get-FileHash "$SourcesDirectory\eng\Localize\LocProject.json").Hash) { Write-PipelineTelemetryError -Category "OneLocBuild" -Message "Existing LocProject.json differs from generated LocProject.json. Download LocProject-generated.json and compare them." exit 1 } else { Write-Host "Generated LocProject.json and current LocProject.json are identical." } } ================================================ FILE: eng/common/helixpublish.proj ================================================ msbuild %(Identity) $(WorkItemDirectory) $(WorkItemCommand) $(WorkItemTimeout) ================================================ FILE: eng/common/init-tools-native.cmd ================================================ @echo off powershell -NoProfile -NoLogo -ExecutionPolicy ByPass -command "& """%~dp0init-tools-native.ps1""" %*" exit /b %ErrorLevel% ================================================ FILE: eng/common/init-tools-native.ps1 ================================================ <# .SYNOPSIS Entry point script for installing native tools .DESCRIPTION Reads $RepoRoot\global.json file to determine native assets to install and executes installers for those tools .PARAMETER BaseUri Base file directory or Url from which to acquire tool archives .PARAMETER InstallDirectory Directory to install native toolset. This is a command-line override for the default Install directory precedence order: - InstallDirectory command-line override - NETCOREENG_INSTALL_DIRECTORY environment variable - (default) %USERPROFILE%/.netcoreeng/native .PARAMETER Clean Switch specifying to not install anything, but cleanup native asset folders .PARAMETER Force Clean and then install tools .PARAMETER DownloadRetries Total number of retry attempts .PARAMETER RetryWaitTimeInSeconds Wait time between retry attempts in seconds .PARAMETER GlobalJsonFile File path to global.json file .PARAMETER PathPromotion Optional switch to enable either promote native tools specified in the global.json to the path (in Azure Pipelines) or break the build if a native tool is not found on the path (on a local dev machine) .NOTES #> [CmdletBinding(PositionalBinding=$false)] Param ( [string] $BaseUri = 'https://netcorenativeassets.blob.core.windows.net/resource-packages/external', [string] $InstallDirectory, [switch] $Clean = $False, [switch] $Force = $False, [int] $DownloadRetries = 5, [int] $RetryWaitTimeInSeconds = 30, [string] $GlobalJsonFile, [switch] $PathPromotion ) if (!$GlobalJsonFile) { $GlobalJsonFile = Join-Path (Get-Item $PSScriptRoot).Parent.Parent.FullName 'global.json' } Set-StrictMode -version 2.0 $ErrorActionPreference='Stop' . $PSScriptRoot\pipeline-logging-functions.ps1 Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') try { # Define verbose switch if undefined $Verbose = $VerbosePreference -Eq 'Continue' $EngCommonBaseDir = Join-Path $PSScriptRoot 'native\' $NativeBaseDir = $InstallDirectory if (!$NativeBaseDir) { $NativeBaseDir = CommonLibrary\Get-NativeInstallDirectory } $Env:CommonLibrary_NativeInstallDir = $NativeBaseDir $InstallBin = Join-Path $NativeBaseDir 'bin' $InstallerPath = Join-Path $EngCommonBaseDir 'install-tool.ps1' # Process tools list Write-Host "Processing $GlobalJsonFile" If (-Not (Test-Path $GlobalJsonFile)) { Write-Host "Unable to find '$GlobalJsonFile'" exit 0 } $NativeTools = Get-Content($GlobalJsonFile) -Raw | ConvertFrom-Json | Select-Object -Expand 'native-tools' -ErrorAction SilentlyContinue if ($NativeTools) { if ($PathPromotion -eq $True) { $ArcadeToolsDirectory = "$env:SYSTEMDRIVE\arcade-tools" if (Test-Path $ArcadeToolsDirectory) { # if this directory exists, we should use native tools on machine $NativeTools.PSObject.Properties | ForEach-Object { $ToolName = $_.Name $ToolVersion = $_.Value $InstalledTools = @{} if ((Get-Command "$ToolName" -ErrorAction SilentlyContinue) -eq $null) { if ($ToolVersion -eq "latest") { $ToolVersion = "" } $ToolDirectories = (Get-ChildItem -Path "$ArcadeToolsDirectory" -Filter "$ToolName-$ToolVersion*" | Sort-Object -Descending) if ($ToolDirectories -eq $null) { Write-Error "Unable to find directory for $ToolName $ToolVersion; please make sure the tool is installed on this image." exit 1 } $ToolDirectory = $ToolDirectories[0] $BinPathFile = "$($ToolDirectory.FullName)\binpath.txt" if (-not (Test-Path -Path "$BinPathFile")) { Write-Error "Unable to find binpath.txt in '$($ToolDirectory.FullName)' ($ToolName $ToolVersion); artifact is either installed incorrectly or is not a bootstrappable tool." exit 1 } $BinPath = Get-Content "$BinPathFile" $ToolPath = Convert-Path -Path $BinPath Write-Host "Adding $ToolName to the path ($ToolPath)..." Write-Host "##vso[task.prependpath]$ToolPath" $env:PATH = "$ToolPath;$env:PATH" $InstalledTools += @{ $ToolName = $ToolDirectory.FullName } } } return $InstalledTools } else { $NativeTools.PSObject.Properties | ForEach-Object { $ToolName = $_.Name $ToolVersion = $_.Value if ((Get-Command "$ToolName" -ErrorAction SilentlyContinue) -eq $null) { Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message "$ToolName not found on path. Please install $ToolName $ToolVersion before proceeding." Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message "If this is running on a build machine, the arcade-tools directory was not found, which means there's an error with the image." } } exit 0 } } else { $NativeTools.PSObject.Properties | ForEach-Object { $ToolName = $_.Name $ToolVersion = $_.Value $LocalInstallerArguments = @{ ToolName = "$ToolName" } $LocalInstallerArguments += @{ InstallPath = "$InstallBin" } $LocalInstallerArguments += @{ BaseUri = "$BaseUri" } $LocalInstallerArguments += @{ CommonLibraryDirectory = "$EngCommonBaseDir" } $LocalInstallerArguments += @{ Version = "$ToolVersion" } if ($Verbose) { $LocalInstallerArguments += @{ Verbose = $True } } if (Get-Variable 'Force' -ErrorAction 'SilentlyContinue') { if($Force) { $LocalInstallerArguments += @{ Force = $True } } } if ($Clean) { $LocalInstallerArguments += @{ Clean = $True } } Write-Verbose "Installing $ToolName version $ToolVersion" Write-Verbose "Executing '$InstallerPath $($LocalInstallerArguments.Keys.ForEach({"-$_ '$($LocalInstallerArguments.$_)'"}) -join ' ')'" & $InstallerPath @LocalInstallerArguments if ($LASTEXITCODE -Ne "0") { $errMsg = "$ToolName installation failed" if ((Get-Variable 'DoNotAbortNativeToolsInstallationOnFailure' -ErrorAction 'SilentlyContinue') -and $DoNotAbortNativeToolsInstallationOnFailure) { $showNativeToolsWarning = $true if ((Get-Variable 'DoNotDisplayNativeToolsInstallationWarnings' -ErrorAction 'SilentlyContinue') -and $DoNotDisplayNativeToolsInstallationWarnings) { $showNativeToolsWarning = $false } if ($showNativeToolsWarning) { Write-Warning $errMsg } $toolInstallationFailure = $true } else { # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 Write-Host $errMsg exit 1 } } } if ((Get-Variable 'toolInstallationFailure' -ErrorAction 'SilentlyContinue') -and $toolInstallationFailure) { # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 Write-Host 'Native tools bootstrap failed' exit 1 } } } else { Write-Host 'No native tools defined in global.json' exit 0 } if ($Clean) { exit 0 } if (Test-Path $InstallBin) { Write-Host 'Native tools are available from ' (Convert-Path -Path $InstallBin) Write-Host "##vso[task.prependpath]$(Convert-Path -Path $InstallBin)" return $InstallBin } elseif (-not ($PathPromotion)) { Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message 'Native tools install directory does not exist, installation failed' exit 1 } exit 0 } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/init-tools-native.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" base_uri='https://netcorenativeassets.blob.core.windows.net/resource-packages/external' install_directory='' clean=false force=false download_retries=5 retry_wait_time_seconds=30 global_json_file="$(dirname "$(dirname "${scriptroot}")")/global.json" declare -a native_assets . $scriptroot/pipeline-logging-functions.sh . $scriptroot/native/common-library.sh while (($# > 0)); do lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --baseuri) base_uri=$2 shift 2 ;; --installdirectory) install_directory=$2 shift 2 ;; --clean) clean=true shift 1 ;; --force) force=true shift 1 ;; --donotabortonfailure) donotabortonfailure=true shift 1 ;; --donotdisplaywarnings) donotdisplaywarnings=true shift 1 ;; --downloadretries) download_retries=$2 shift 2 ;; --retrywaittimeseconds) retry_wait_time_seconds=$2 shift 2 ;; --help) echo "Common settings:" echo " --installdirectory Directory to install native toolset." echo " This is a command-line override for the default" echo " Install directory precedence order:" echo " - InstallDirectory command-line override" echo " - NETCOREENG_INSTALL_DIRECTORY environment variable" echo " - (default) %USERPROFILE%/.netcoreeng/native" echo "" echo " --clean Switch specifying not to install anything, but cleanup native asset folders" echo " --donotabortonfailure Switch specifiying whether to abort native tools installation on failure" echo " --donotdisplaywarnings Switch specifiying whether to display warnings during native tools installation on failure" echo " --force Clean and then install tools" echo " --help Print help and exit" echo "" echo "Advanced settings:" echo " --baseuri Base URI for where to download native tools from" echo " --downloadretries Number of times a download should be attempted" echo " --retrywaittimeseconds Wait time between download attempts" echo "" exit 0 ;; esac done function ReadGlobalJsonNativeTools { # happy path: we have a proper JSON parsing tool `jq(1)` in PATH! if command -v jq &> /dev/null; then # jq: read each key/value pair under "native-tools" entry and emit: # KEY="" VALUE="" # followed by a null byte. # # bash: read line with null byte delimeter and push to array (for later `eval`uation). while IFS= read -rd '' line; do native_assets+=("$line") done < <(jq -r '. | select(has("native-tools")) | ."native-tools" | keys[] as $k | @sh "KEY=\($k) VALUE=\(.[$k])\u0000"' "$global_json_file") return fi # Warning: falling back to manually parsing JSON, which is not recommended. # Following routine matches the output and escaping logic of jq(1)'s @sh formatter used above. # It has been tested with several weird strings with escaped characters in entries (key and value) # and results were compared with the output of jq(1) in binary representation using xxd(1); # just before the assignment to 'native_assets' array (above and below). # try to capture the section under "native-tools". if [[ ! "$(cat "$global_json_file")" =~ \"native-tools\"[[:space:]\:\{]*([^\}]+) ]]; then return fi section="${BASH_REMATCH[1]}" parseStarted=0 possibleEnd=0 escaping=0 escaped=0 isKey=1 for (( i=0; i<${#section}; i++ )); do char="${section:$i:1}" if ! ((parseStarted)) && [[ "$char" =~ [[:space:],:] ]]; then continue; fi if ! ((escaping)) && [[ "$char" == "\\" ]]; then escaping=1 elif ((escaping)) && ! ((escaped)); then escaped=1 fi if ! ((parseStarted)) && [[ "$char" == "\"" ]]; then parseStarted=1 possibleEnd=0 elif [[ "$char" == "'" ]]; then token="$token'\\\''" possibleEnd=0 elif ((escaping)) || [[ "$char" != "\"" ]]; then token="$token$char" possibleEnd=1 fi if ((possibleEnd)) && ! ((escaping)) && [[ "$char" == "\"" ]]; then # Use printf to unescape token to match jq(1)'s @sh formatting rules. # do not use 'token="$(printf "$token")"' syntax, as $() eats the trailing linefeed. printf -v token "'$token'" if ((isKey)); then KEY="$token" isKey=0 else line="KEY=$KEY VALUE=$token" native_assets+=("$line") isKey=1 fi # reset for next token parseStarted=0 token= elif ((escaping)) && ((escaped)); then escaping=0 escaped=0 fi done } native_base_dir=$install_directory if [[ -z $install_directory ]]; then native_base_dir=$(GetNativeInstallDirectory) fi install_bin="${native_base_dir}/bin" installed_any=false ReadGlobalJsonNativeTools if [[ ${#native_assets[@]} -eq 0 ]]; then echo "No native tools defined in global.json" exit 0; else native_installer_dir="$scriptroot/native" for index in "${!native_assets[@]}"; do eval "${native_assets["$index"]}" installer_path="$native_installer_dir/install-$KEY.sh" installer_command="$installer_path" installer_command+=" --baseuri $base_uri" installer_command+=" --installpath $install_bin" installer_command+=" --version $VALUE" echo $installer_command if [[ $force = true ]]; then installer_command+=" --force" fi if [[ $clean = true ]]; then installer_command+=" --clean" fi if [[ -a $installer_path ]]; then $installer_command if [[ $? != 0 ]]; then if [[ $donotabortonfailure = true ]]; then if [[ $donotdisplaywarnings != true ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" fi else Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" exit 1 fi else $installed_any = true fi else if [[ $donotabortonfailure == true ]]; then if [[ $donotdisplaywarnings != true ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" fi else Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" exit 1 fi fi done fi if [[ $clean = true ]]; then exit 0 fi if [[ -d $install_bin ]]; then echo "Native tools are available from $install_bin" echo "##vso[task.prependpath]$install_bin" else if [[ $installed_any = true ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Native tools install directory does not exist, installation failed" exit 1 fi fi exit 0 ================================================ FILE: eng/common/internal/Directory.Build.props ================================================ false false ================================================ FILE: eng/common/internal/NuGet.config ================================================ ================================================ FILE: eng/common/internal/Tools.csproj ================================================ net472 false false ================================================ FILE: eng/common/internal-feed-operations.ps1 ================================================ param( [Parameter(Mandatory=$true)][string] $Operation, [string] $AuthToken, [string] $CommitSha, [string] $RepoName, [switch] $IsFeedPrivate ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 . $PSScriptRoot\tools.ps1 # Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed # in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in # https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. This should ONLY be called from identified # internal builds function SetupCredProvider { param( [string] $AuthToken ) # Install the Cred Provider NuGet plugin Write-Host 'Setting up Cred Provider NuGet plugin in the agent...' Write-Host "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." $url = 'https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1' Write-Host "Writing the contents of 'installcredprovider.ps1' locally..." Invoke-WebRequest $url -UseBasicParsing -OutFile installcredprovider.ps1 Write-Host 'Installing plugin...' .\installcredprovider.ps1 -Force Write-Host "Deleting local copy of 'installcredprovider.ps1'..." Remove-Item .\installcredprovider.ps1 if (-Not("$env:USERPROFILE\.nuget\plugins\netcore")) { Write-PipelineTelemetryError -Category 'Arcade' -Message 'CredProvider plugin was not installed correctly!' ExitWithExitCode 1 } else { Write-Host 'CredProvider plugin was installed correctly!' } # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable # feeds successfully $nugetConfigPath = Join-Path $RepoRoot "NuGet.config" if (-Not (Test-Path -Path $nugetConfigPath)) { Write-PipelineTelemetryError -Category 'Build' -Message 'NuGet.config file not found in repo root!' ExitWithExitCode 1 } $endpoints = New-Object System.Collections.ArrayList $nugetConfigPackageSources = Select-Xml -Path $nugetConfigPath -XPath "//packageSources/add[contains(@key, 'darc-int-')]/@value" | foreach{$_.Node.Value} if (($nugetConfigPackageSources | Measure-Object).Count -gt 0 ) { foreach ($stableRestoreResource in $nugetConfigPackageSources) { $trimmedResource = ([string]$stableRestoreResource).Trim() [void]$endpoints.Add(@{endpoint="$trimmedResource"; password="$AuthToken"}) } } if (($endpoints | Measure-Object).Count -gt 0) { $endpointCredentials = @{endpointCredentials=$endpoints} | ConvertTo-Json -Compress # Create the environment variables the AzDo way Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $endpointCredentials -Properties @{ 'variable' = 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' 'issecret' = 'false' } # We don't want sessions cached since we will be updating the endpoints quite frequently Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data 'False' -Properties @{ 'variable' = 'NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED' 'issecret' = 'false' } } else { Write-Host 'No internal endpoints found in NuGet.config' } } #Workaround for https://github.com/microsoft/msbuild/issues/4430 function InstallDotNetSdkAndRestoreArcade { $dotnetTempDir = Join-Path $RepoRoot "dotnet" $dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) $dotnet = "$dotnetTempDir\dotnet.exe" $restoreProjPath = "$PSScriptRoot\restore.proj" Write-Host "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" '' | Out-File "$restoreProjPath" & $dotnet restore $restoreProjPath Write-Host 'Arcade SDK restored!' if (Test-Path -Path $restoreProjPath) { Remove-Item $restoreProjPath } if (Test-Path -Path $dotnetTempDir) { Remove-Item $dotnetTempDir -Recurse } } try { Push-Location $PSScriptRoot if ($Operation -like 'setup') { SetupCredProvider $AuthToken } elseif ($Operation -like 'install-restore') { InstallDotNetSdkAndRestoreArcade } else { Write-PipelineTelemetryError -Category 'Arcade' -Message "Unknown operation '$Operation'!" ExitWithExitCode 1 } } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'Arcade' -Message $_ ExitWithExitCode 1 } finally { Pop-Location } ================================================ FILE: eng/common/internal-feed-operations.sh ================================================ #!/usr/bin/env bash set -e # Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed # in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in # https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. # This should ONLY be called from identified internal builds function SetupCredProvider { local authToken=$1 # Install the Cred Provider NuGet plugin echo "Setting up Cred Provider NuGet plugin in the agent..."... echo "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." local url="https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh" echo "Writing the contents of 'installcredprovider.ps1' locally..." local installcredproviderPath="installcredprovider.sh" if command -v curl > /dev/null; then curl $url > "$installcredproviderPath" else wget -q -O "$installcredproviderPath" "$url" fi echo "Installing plugin..." . "$installcredproviderPath" echo "Deleting local copy of 'installcredprovider.sh'..." rm installcredprovider.sh if [ ! -d "$HOME/.nuget/plugins" ]; then Write-PipelineTelemetryError -category 'Build' 'CredProvider plugin was not installed correctly!' ExitWithExitCode 1 else echo "CredProvider plugin was installed correctly!" fi # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable # feeds successfully local nugetConfigPath="{$repo_root}NuGet.config" if [ ! "$nugetConfigPath" ]; then Write-PipelineTelemetryError -category 'Build' "NuGet.config file not found in repo's root!" ExitWithExitCode 1 fi local endpoints='[' local nugetConfigPackageValues=`cat "$nugetConfigPath" | grep "key=\"darc-int-"` local pattern="value=\"(.*)\"" for value in $nugetConfigPackageValues do if [[ $value =~ $pattern ]]; then local endpoint="${BASH_REMATCH[1]}" endpoints+="{\"endpoint\": \"$endpoint\", \"password\": \"$authToken\"}," fi done endpoints=${endpoints%?} endpoints+=']' if [ ${#endpoints} -gt 2 ]; then local endpointCredentials="{\"endpointCredentials\": "$endpoints"}" echo "##vso[task.setvariable variable=VSS_NUGET_EXTERNAL_FEED_ENDPOINTS]$endpointCredentials" echo "##vso[task.setvariable variable=NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED]False" else echo "No internal endpoints found in NuGet.config" fi } # Workaround for https://github.com/microsoft/msbuild/issues/4430 function InstallDotNetSdkAndRestoreArcade { local dotnetTempDir="$repo_root/dotnet" local dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) local restoreProjPath="$repo_root/eng/common/restore.proj" echo "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." echo "" > "$restoreProjPath" InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" local res=`$dotnetTempDir/dotnet restore $restoreProjPath` echo "Arcade SDK restored!" # Cleanup if [ "$restoreProjPath" ]; then rm "$restoreProjPath" fi if [ "$dotnetTempDir" ]; then rm -r $dotnetTempDir fi } source="${BASH_SOURCE[0]}" operation='' authToken='' repoName='' while [[ $# -gt 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --operation) operation=$2 shift ;; --authtoken) authToken=$2 shift ;; *) echo "Invalid argument: $1" usage exit 1 ;; esac shift done while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . "$scriptroot/tools.sh" if [ "$operation" = "setup" ]; then SetupCredProvider $authToken elif [ "$operation" = "install-restore" ]; then InstallDotNetSdkAndRestoreArcade else echo "Unknown operation '$operation'!" fi ================================================ FILE: eng/common/loc/P22DotNetHtmlLocalization.lss ================================================ ================================================ FILE: eng/common/msbuild.ps1 ================================================ [CmdletBinding(PositionalBinding=$false)] Param( [string] $verbosity = 'minimal', [bool] $warnAsError = $true, [bool] $nodeReuse = $true, [switch] $ci, [switch] $prepareMachine, [switch] $excludePrereleaseVS, [string] $msbuildEngine = $null, [Parameter(ValueFromRemainingArguments=$true)][String[]]$extraArgs ) . $PSScriptRoot\tools.ps1 try { if ($ci) { $nodeReuse = $false } MSBuild @extraArgs } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'Build' -Message $_ ExitWithExitCode 1 } ExitWithExitCode 0 ================================================ FILE: eng/common/msbuild.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" verbosity='minimal' warn_as_error=true node_reuse=true prepare_machine=false extra_args='' while (($# > 0)); do lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --verbosity) verbosity=$2 shift 2 ;; --warnaserror) warn_as_error=$2 shift 2 ;; --nodereuse) node_reuse=$2 shift 2 ;; --ci) ci=true shift 1 ;; --preparemachine) prepare_machine=true shift 1 ;; *) extra_args="$extra_args $1" shift 1 ;; esac done . "$scriptroot/tools.sh" if [[ "$ci" == true ]]; then node_reuse=false fi MSBuild $extra_args ExitWithExitCode 0 ================================================ FILE: eng/common/native/CommonLibrary.psm1 ================================================ <# .SYNOPSIS Helper module to install an archive to a directory .DESCRIPTION Helper module to download and extract an archive to a specified directory .PARAMETER Uri Uri of artifact to download .PARAMETER InstallDirectory Directory to extract artifact contents to .PARAMETER Force Force download / extraction if file or contents already exist. Default = False .PARAMETER DownloadRetries Total number of retry attempts. Default = 5 .PARAMETER RetryWaitTimeInSeconds Wait time between retry attempts in seconds. Default = 30 .NOTES Returns False if download or extraction fail, True otherwise #> function DownloadAndExtract { [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $Uri, [Parameter(Mandatory=$True)] [string] $InstallDirectory, [switch] $Force = $False, [int] $DownloadRetries = 5, [int] $RetryWaitTimeInSeconds = 30 ) # Define verbose switch if undefined $Verbose = $VerbosePreference -Eq "Continue" $TempToolPath = CommonLibrary\Get-TempPathFilename -Path $Uri # Download native tool $DownloadStatus = CommonLibrary\Get-File -Uri $Uri ` -Path $TempToolPath ` -DownloadRetries $DownloadRetries ` -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` -Force:$Force ` -Verbose:$Verbose if ($DownloadStatus -Eq $False) { Write-Error "Download failed from $Uri" return $False } # Extract native tool $UnzipStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath ` -OutputDirectory $InstallDirectory ` -Force:$Force ` -Verbose:$Verbose if ($UnzipStatus -Eq $False) { # Retry Download one more time with Force=true $DownloadRetryStatus = CommonLibrary\Get-File -Uri $Uri ` -Path $TempToolPath ` -DownloadRetries 1 ` -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` -Force:$True ` -Verbose:$Verbose if ($DownloadRetryStatus -Eq $False) { Write-Error "Last attempt of download failed as well" return $False } # Retry unzip again one more time with Force=true $UnzipRetryStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath ` -OutputDirectory $InstallDirectory ` -Force:$True ` -Verbose:$Verbose if ($UnzipRetryStatus -Eq $False) { Write-Error "Last attempt of unzip failed as well" # Clean up partial zips and extracts if (Test-Path $TempToolPath) { Remove-Item $TempToolPath -Force } if (Test-Path $InstallDirectory) { Remove-Item $InstallDirectory -Force -Recurse } return $False } } return $True } <# .SYNOPSIS Download a file, retry on failure .DESCRIPTION Download specified file and retry if attempt fails .PARAMETER Uri Uri of file to download. If Uri is a local path, the file will be copied instead of downloaded .PARAMETER Path Path to download or copy uri file to .PARAMETER Force Overwrite existing file if present. Default = False .PARAMETER DownloadRetries Total number of retry attempts. Default = 5 .PARAMETER RetryWaitTimeInSeconds Wait time between retry attempts in seconds Default = 30 #> function Get-File { [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $Uri, [Parameter(Mandatory=$True)] [string] $Path, [int] $DownloadRetries = 5, [int] $RetryWaitTimeInSeconds = 30, [switch] $Force = $False ) $Attempt = 0 if ($Force) { if (Test-Path $Path) { Remove-Item $Path -Force } } if (Test-Path $Path) { Write-Host "File '$Path' already exists, skipping download" return $True } $DownloadDirectory = Split-Path -ErrorAction Ignore -Path "$Path" -Parent if (-Not (Test-Path $DownloadDirectory)) { New-Item -path $DownloadDirectory -force -itemType "Directory" | Out-Null } $TempPath = "$Path.tmp" if (Test-Path -IsValid -Path $Uri) { Write-Verbose "'$Uri' is a file path, copying temporarily to '$TempPath'" Copy-Item -Path $Uri -Destination $TempPath Write-Verbose "Moving temporary file to '$Path'" Move-Item -Path $TempPath -Destination $Path return $? } else { Write-Verbose "Downloading $Uri" # Don't display the console progress UI - it's a huge perf hit $ProgressPreference = 'SilentlyContinue' while($Attempt -Lt $DownloadRetries) { try { Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $TempPath Write-Verbose "Downloaded to temporary location '$TempPath'" Move-Item -Path $TempPath -Destination $Path Write-Verbose "Moved temporary file to '$Path'" return $True } catch { $Attempt++ if ($Attempt -Lt $DownloadRetries) { $AttemptsLeft = $DownloadRetries - $Attempt Write-Warning "Download failed, $AttemptsLeft attempts remaining, will retry in $RetryWaitTimeInSeconds seconds" Start-Sleep -Seconds $RetryWaitTimeInSeconds } else { Write-Error $_ Write-Error $_.Exception } } } } return $False } <# .SYNOPSIS Generate a shim for a native tool .DESCRIPTION Creates a wrapper script (shim) that passes arguments forward to native tool assembly .PARAMETER ShimName The name of the shim .PARAMETER ShimDirectory The directory where shims are stored .PARAMETER ToolFilePath Path to file that shim forwards to .PARAMETER Force Replace shim if already present. Default = False .NOTES Returns $True if generating shim succeeds, $False otherwise #> function New-ScriptShim { [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $ShimName, [Parameter(Mandatory=$True)] [string] $ShimDirectory, [Parameter(Mandatory=$True)] [string] $ToolFilePath, [Parameter(Mandatory=$True)] [string] $BaseUri, [switch] $Force ) try { Write-Verbose "Generating '$ShimName' shim" if (-Not (Test-Path $ToolFilePath)){ Write-Error "Specified tool file path '$ToolFilePath' does not exist" return $False } # WinShimmer is a small .NET Framework program that creates .exe shims to bootstrapped programs # Many of the checks for installed programs expect a .exe extension for Windows tools, rather # than a .bat or .cmd file. # Source: https://github.com/dotnet/arcade/tree/master/src/WinShimmer if (-Not (Test-Path "$ShimDirectory\WinShimmer\winshimmer.exe")) { $InstallStatus = DownloadAndExtract -Uri "$BaseUri/windows/winshimmer/WinShimmer.zip" ` -InstallDirectory $ShimDirectory\WinShimmer ` -Force:$Force ` -DownloadRetries 2 ` -RetryWaitTimeInSeconds 5 ` -Verbose:$Verbose } if ((Test-Path (Join-Path $ShimDirectory "$ShimName.exe"))) { Write-Host "$ShimName.exe already exists; replacing..." Remove-Item (Join-Path $ShimDirectory "$ShimName.exe") } & "$ShimDirectory\WinShimmer\winshimmer.exe" $ShimName $ToolFilePath $ShimDirectory return $True } catch { Write-Host $_ Write-Host $_.Exception return $False } } <# .SYNOPSIS Returns the machine architecture of the host machine .NOTES Returns 'x64' on 64 bit machines Returns 'x86' on 32 bit machines #> function Get-MachineArchitecture { $ProcessorArchitecture = $Env:PROCESSOR_ARCHITECTURE $ProcessorArchitectureW6432 = $Env:PROCESSOR_ARCHITEW6432 if($ProcessorArchitecture -Eq "X86") { if(($ProcessorArchitectureW6432 -Eq "") -Or ($ProcessorArchitectureW6432 -Eq "X86")) { return "x86" } $ProcessorArchitecture = $ProcessorArchitectureW6432 } if (($ProcessorArchitecture -Eq "AMD64") -Or ($ProcessorArchitecture -Eq "IA64") -Or ($ProcessorArchitecture -Eq "ARM64") -Or ($ProcessorArchitecture -Eq "LOONGARCH64") -Or ($ProcessorArchitecture -Eq "RISCV64")) { return "x64" } return "x86" } <# .SYNOPSIS Get the name of a temporary folder under the native install directory #> function Get-TempDirectory { return Join-Path (Get-NativeInstallDirectory) "temp/" } function Get-TempPathFilename { [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $Path ) $TempDir = CommonLibrary\Get-TempDirectory $TempFilename = Split-Path $Path -leaf $TempPath = Join-Path $TempDir $TempFilename return $TempPath } <# .SYNOPSIS Returns the base directory to use for native tool installation .NOTES Returns the value of the NETCOREENG_INSTALL_DIRECTORY if that environment variable is set, or otherwise returns an install directory under the %USERPROFILE% #> function Get-NativeInstallDirectory { $InstallDir = $Env:NETCOREENG_INSTALL_DIRECTORY if (!$InstallDir) { $InstallDir = Join-Path $Env:USERPROFILE ".netcoreeng/native/" } return $InstallDir } <# .SYNOPSIS Unzip an archive .DESCRIPTION Powershell module to unzip an archive to a specified directory .PARAMETER ZipPath (Required) Path to archive to unzip .PARAMETER OutputDirectory (Required) Output directory for archive contents .PARAMETER Force Overwrite output directory contents if they already exist .NOTES - Returns True and does not perform an extraction if output directory already exists but Overwrite is not True. - Returns True if unzip operation is successful - Returns False if Overwrite is True and it is unable to remove contents of OutputDirectory - Returns False if unable to extract zip archive #> function Expand-Zip { [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $ZipPath, [Parameter(Mandatory=$True)] [string] $OutputDirectory, [switch] $Force ) Write-Verbose "Extracting '$ZipPath' to '$OutputDirectory'" try { if ((Test-Path $OutputDirectory) -And (-Not $Force)) { Write-Host "Directory '$OutputDirectory' already exists, skipping extract" return $True } if (Test-Path $OutputDirectory) { Write-Verbose "'Force' is 'True', but '$OutputDirectory' exists, removing directory" Remove-Item $OutputDirectory -Force -Recurse if ($? -Eq $False) { Write-Error "Unable to remove '$OutputDirectory'" return $False } } $TempOutputDirectory = Join-Path "$(Split-Path -Parent $OutputDirectory)" "$(Split-Path -Leaf $OutputDirectory).tmp" if (Test-Path $TempOutputDirectory) { Remove-Item $TempOutputDirectory -Force -Recurse } New-Item -Path $TempOutputDirectory -Force -ItemType "Directory" | Out-Null Add-Type -assembly "system.io.compression.filesystem" [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$TempOutputDirectory") if ($? -Eq $False) { Write-Error "Unable to extract '$ZipPath'" return $False } Move-Item -Path $TempOutputDirectory -Destination $OutputDirectory } catch { Write-Host $_ Write-Host $_.Exception return $False } return $True } export-modulemember -function DownloadAndExtract export-modulemember -function Expand-Zip export-modulemember -function Get-File export-modulemember -function Get-MachineArchitecture export-modulemember -function Get-NativeInstallDirectory export-modulemember -function Get-TempDirectory export-modulemember -function Get-TempPathFilename export-modulemember -function New-ScriptShim ================================================ FILE: eng/common/native/common-library.sh ================================================ #!/usr/bin/env bash function GetNativeInstallDirectory { local install_dir if [[ -z $NETCOREENG_INSTALL_DIRECTORY ]]; then install_dir=$HOME/.netcoreeng/native/ else install_dir=$NETCOREENG_INSTALL_DIRECTORY fi echo $install_dir return 0 } function GetTempDirectory { echo $(GetNativeInstallDirectory)temp/ return 0 } function ExpandZip { local zip_path=$1 local output_directory=$2 local force=${3:-false} echo "Extracting $zip_path to $output_directory" if [[ -d $output_directory ]] && [[ $force = false ]]; then echo "Directory '$output_directory' already exists, skipping extract" return 0 fi if [[ -d $output_directory ]]; then echo "'Force flag enabled, but '$output_directory' exists. Removing directory" rm -rf $output_directory if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to remove '$output_directory'" return 1 fi fi echo "Creating directory: '$output_directory'" mkdir -p $output_directory echo "Extracting archive" tar -xf $zip_path -C $output_directory if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to extract '$zip_path'" return 1 fi return 0 } function GetCurrentOS { local unameOut="$(uname -s)" case $unameOut in Linux*) echo "Linux";; Darwin*) echo "MacOS";; esac return 0 } function GetFile { local uri=$1 local path=$2 local force=${3:-false} local download_retries=${4:-5} local retry_wait_time_seconds=${5:-30} if [[ -f $path ]]; then if [[ $force = false ]]; then echo "File '$path' already exists. Skipping download" return 0 else rm -rf $path fi fi if [[ -f $uri ]]; then echo "'$uri' is a file path, copying file to '$path'" cp $uri $path return $? fi echo "Downloading $uri" # Use curl if available, otherwise use wget if command -v curl > /dev/null; then curl "$uri" -sSL --retry $download_retries --retry-delay $retry_wait_time_seconds --create-dirs -o "$path" --fail else wget -q -O "$path" "$uri" --tries="$download_retries" fi return $? } function GetTempPathFileName { local path=$1 local temp_dir=$(GetTempDirectory) local temp_file_name=$(basename $path) echo $temp_dir$temp_file_name return 0 } function DownloadAndExtract { local uri=$1 local installDir=$2 local force=${3:-false} local download_retries=${4:-5} local retry_wait_time_seconds=${5:-30} local temp_tool_path=$(GetTempPathFileName $uri) echo "downloading to: $temp_tool_path" # Download file GetFile "$uri" "$temp_tool_path" $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to download '$uri' to '$temp_tool_path'." return 1 fi # Extract File echo "extracting from $temp_tool_path to $installDir" ExpandZip "$temp_tool_path" "$installDir" $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to extract '$temp_tool_path' to '$installDir'." return 1 fi return 0 } function NewScriptShim { local shimpath=$1 local tool_file_path=$2 local force=${3:-false} echo "Generating '$shimpath' shim" if [[ -f $shimpath ]]; then if [[ $force = false ]]; then echo "File '$shimpath' already exists." >&2 return 1 else rm -rf $shimpath fi fi if [[ ! -f $tool_file_path ]]; then # try to see if the path is lower cased tool_file_path="$(echo $tool_file_path | tr "[:upper:]" "[:lower:]")" if [[ ! -f $tool_file_path ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Specified tool file path:'$tool_file_path' does not exist" return 1 fi fi local shim_contents=$'#!/usr/bin/env bash\n' shim_contents+="SHIMARGS="$'$1\n' shim_contents+="$tool_file_path"$' $SHIMARGS\n' # Write shim file echo "$shim_contents" > $shimpath chmod +x $shimpath echo "Finished generating shim '$shimpath'" return $? } ================================================ FILE: eng/common/native/init-compiler.sh ================================================ #!/bin/sh # # This file detects the C/C++ compiler and exports it to the CC/CXX environment variables # # NOTE: some scripts source this file and rely on stdout being empty, make sure # to not output *anything* here, unless it is an error message that fails the # build. if [ -z "$build_arch" ] || [ -z "$compiler" ]; then echo "Usage..." echo "build_arch= compiler= init-compiler.sh" echo "Specify the target architecture." echo "Specify the name of compiler (clang or gcc)." exit 1 fi case "$compiler" in clang*|-clang*|--clang*) # clangx.y or clang-x.y version="$(echo "$compiler" | tr -d '[:alpha:]-=')" majorVersion="${version%%.*}" # LLVM based on v18 released in early 2024, with two releases per year maxVersion="$((18 + ((($(date +%Y) - 2024) * 12 + $(date +%-m) - 3) / 6)))" compiler=clang ;; gcc*|-gcc*|--gcc*) # gccx.y or gcc-x.y version="$(echo "$compiler" | tr -d '[:alpha:]-=')" majorVersion="${version%%.*}" # GCC based on v14 released in early 2024, with one release per year maxVersion="$((14 + ((($(date +%Y) - 2024) * 12 + $(date +%-m) - 3) / 12)))" compiler=gcc ;; esac cxxCompiler="$compiler++" # clear the existing CC and CXX from environment CC= CXX= LDFLAGS= if [ "$compiler" = "gcc" ]; then cxxCompiler="g++"; fi check_version_exists() { desired_version=-1 # Set up the environment to be used for building with the desired compiler. if command -v "$compiler-$1" > /dev/null; then desired_version="-$1" elif command -v "$compiler$1" > /dev/null; then desired_version="$1" fi echo "$desired_version" } __baseOS="$(uname)" set_compiler_version_from_CC() { if [ "$__baseOS" = "Darwin" ]; then # On Darwin, the versions from -version/-dumpversion refer to Xcode # versions, not llvm versions, so we can't rely on them. return fi version="$("$CC" -dumpversion)" if [ -z "$version" ]; then echo "Error: $CC -dumpversion didn't provide a version" exit 1 fi # gcc and clang often display 3 part versions. However, gcc can show only 1 part in some environments. IFS=. read -r majorVersion _ < /dev/null; then echo "Error: No compatible version of $compiler was found within the range of $minVersion to $maxVersion. Please upgrade your toolchain or specify the compiler explicitly using CLR_CC and CLR_CXX environment variables." exit 1 fi CC="$(command -v "$compiler" 2> /dev/null)" CXX="$(command -v "$cxxCompiler" 2> /dev/null)" set_compiler_version_from_CC fi else desired_version="$(check_version_exists "$majorVersion")" if [ "$desired_version" = "-1" ]; then echo "Error: Could not find specific version of $compiler: $majorVersion." exit 1 fi fi if [ -z "$CC" ]; then CC="$(command -v "$compiler$desired_version" 2> /dev/null)" CXX="$(command -v "$cxxCompiler$desired_version" 2> /dev/null)" if [ -z "$CXX" ]; then CXX="$(command -v "$cxxCompiler" 2> /dev/null)"; fi set_compiler_version_from_CC fi else if [ ! -f "$CLR_CC" ]; then echo "Error: CLR_CC is set but path '$CLR_CC' does not exist" exit 1 fi CC="$CLR_CC" CXX="$CLR_CXX" set_compiler_version_from_CC fi if [ -z "$CC" ]; then echo "Error: Unable to find $compiler." exit 1 fi if [ "$__baseOS" != "Darwin" ]; then # On Darwin, we always want to use the Apple linker. # Only lld version >= 9 can be considered stable. lld supports s390x starting from 18.0. if [ "$compiler" = "clang" ] && [ -n "$majorVersion" ] && [ "$majorVersion" -ge 9 ] && { [ "$build_arch" != "s390x" ] || [ "$majorVersion" -ge 18 ]; }; then if "$CC" -fuse-ld=lld -Wl,--version >/dev/null 2>&1; then LDFLAGS="-fuse-ld=lld" fi fi fi SCAN_BUILD_COMMAND="$(command -v "scan-build$desired_version" 2> /dev/null)" export CC CXX LDFLAGS SCAN_BUILD_COMMAND ================================================ FILE: eng/common/native/init-distro-rid.sh ================================================ #!/bin/sh # getNonPortableDistroRid # # Input: # targetOs: (str) # targetArch: (str) # rootfsDir: (str) # # Return: # non-portable rid getNonPortableDistroRid() { targetOs="$1" targetArch="$2" rootfsDir="$3" nonPortableRid="" if [ "$targetOs" = "linux" ]; then # shellcheck disable=SC1091 if [ -e "${rootfsDir}/etc/os-release" ]; then . "${rootfsDir}/etc/os-release" if echo "${VERSION_ID:-}" | grep -qE '^([[:digit:]]|\.)+$'; then nonPortableRid="${ID}.${VERSION_ID}-${targetArch}" else # Rolling release distros either do not set VERSION_ID, set it as blank or # set it to non-version looking string (such as TEMPLATE_VERSION_ID on ArchLinux); # so omit it here to be consistent with everything else. nonPortableRid="${ID}-${targetArch}" fi elif [ -e "${rootfsDir}/android_platform" ]; then # shellcheck disable=SC1091 . "${rootfsDir}/android_platform" nonPortableRid="$RID" fi fi if [ "$targetOs" = "freebsd" ]; then # $rootfsDir can be empty. freebsd-version is a shell script and should always work. __freebsd_major_version=$("$rootfsDir"/bin/freebsd-version | cut -d'.' -f1) nonPortableRid="freebsd.$__freebsd_major_version-${targetArch}" elif [ "$targetOs" = "openbsd" ]; then nonPortableRid="openbsd.$(uname -r)-${targetArch}" elif command -v getprop >/dev/null && getprop ro.product.system.model | grep -qi android; then __android_sdk_version=$(getprop ro.build.version.sdk) nonPortableRid="android.$__android_sdk_version-${targetArch}" elif [ "$targetOs" = "illumos" ]; then __uname_version=$(uname -v) nonPortableRid="illumos-${targetArch}" elif [ "$targetOs" = "solaris" ]; then __uname_version=$(uname -v) __solaris_major_version=$(echo "$__uname_version" | cut -d'.' -f1) nonPortableRid="solaris.$__solaris_major_version-${targetArch}" elif [ "$targetOs" = "haiku" ]; then __uname_release="$(uname -r)" nonPortableRid=haiku.r"$__uname_release"-"$targetArch" fi echo "$nonPortableRid" | tr '[:upper:]' '[:lower:]' } # initDistroRidGlobal # # Input: # os: (str) # arch: (str) # rootfsDir?: (nullable:string) # # Return: # None # # Notes: # It is important to note that the function does not return anything, but it # exports the following variables on success: # __DistroRid : Non-portable rid of the target platform. # __PortableTargetOS : OS-part of the portable rid that corresponds to the target platform. initDistroRidGlobal() { targetOs="$1" targetArch="$2" rootfsDir="" if [ $# -ge 3 ]; then rootfsDir="$3" fi if [ -n "${rootfsDir}" ]; then # We may have a cross build. Check for the existence of the rootfsDir if [ ! -e "${rootfsDir}" ]; then echo "Error: rootfsDir has been passed, but the location is not valid." exit 1 fi fi __DistroRid=$(getNonPortableDistroRid "${targetOs}" "${targetArch}" "${rootfsDir}") if [ -z "${__PortableTargetOS:-}" ]; then __PortableTargetOS="$targetOs" STRINGS="$(command -v strings || true)" if [ -z "$STRINGS" ]; then STRINGS="$(command -v llvm-strings || true)" fi # Check for musl-based distros (e.g. Alpine Linux, Void Linux). if "${rootfsDir}/usr/bin/ldd" --version 2>&1 | grep -q musl || ( [ -n "$STRINGS" ] && "$STRINGS" "${rootfsDir}/usr/bin/ldd" 2>&1 | grep -q musl ); then __PortableTargetOS="linux-musl" fi fi export __DistroRid __PortableTargetOS } ================================================ FILE: eng/common/native/init-os-and-arch.sh ================================================ #!/bin/sh # Use uname to determine what the OS is. OSName=$(uname -s | tr '[:upper:]' '[:lower:]') if command -v getprop && getprop ro.product.system.model 2>&1 | grep -qi android; then OSName="android" fi case "$OSName" in freebsd|linux|netbsd|openbsd|sunos|android|haiku) os="$OSName" ;; darwin) os=osx ;; *) echo "Unsupported OS $OSName detected!" exit 1 ;; esac # On Solaris, `uname -m` is discouraged, see https://docs.oracle.com/cd/E36784_01/html/E36870/uname-1.html # and `uname -p` returns processor type (e.g. i386 on amd64). # The appropriate tool to determine CPU is isainfo(1) https://docs.oracle.com/cd/E36784_01/html/E36870/isainfo-1.html. if [ "$os" = "sunos" ]; then if uname -o 2>&1 | grep -q illumos; then os="illumos" else os="solaris" fi CPUName=$(isainfo -n) else # For the rest of the operating systems, use uname(1) to determine what the CPU is. CPUName=$(uname -m) fi case "$CPUName" in arm64|aarch64) arch=arm64 if [ "$(getconf LONG_BIT)" -lt 64 ]; then # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS) arch=arm fi ;; loongarch64) arch=loongarch64 ;; riscv64) arch=riscv64 ;; amd64|x86_64) arch=x64 ;; armv7l|armv8l) # shellcheck disable=SC1091 if (NAME=""; . /etc/os-release; test "$NAME" = "Tizen"); then arch=armel else arch=arm fi ;; armv6l) arch=armv6 ;; i[3-6]86) echo "Unsupported CPU $CPUName detected, build might not succeed!" arch=x86 ;; s390x) arch=s390x ;; ppc64le) arch=ppc64le ;; *) echo "Unknown CPU $CPUName detected!" exit 1 ;; esac ================================================ FILE: eng/common/native/install-cmake-test.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . $scriptroot/common-library.sh base_uri= install_path= version= clean=false force=false download_retries=5 retry_wait_time_seconds=30 while (($# > 0)); do lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --baseuri) base_uri=$2 shift 2 ;; --installpath) install_path=$2 shift 2 ;; --version) version=$2 shift 2 ;; --clean) clean=true shift 1 ;; --force) force=true shift 1 ;; --downloadretries) download_retries=$2 shift 2 ;; --retrywaittimeseconds) retry_wait_time_seconds=$2 shift 2 ;; --help) echo "Common settings:" echo " --baseuri Base file directory or Url wrom which to acquire tool archives" echo " --installpath Base directory to install native tool to" echo " --clean Don't install the tool, just clean up the current install of the tool" echo " --force Force install of tools even if they previously exist" echo " --help Print help and exit" echo "" echo "Advanced settings:" echo " --downloadretries Total number of retry attempts" echo " --retrywaittimeseconds Wait time between retry attempts in seconds" echo "" exit 0 ;; esac done tool_name="cmake-test" tool_os=$(GetCurrentOS) tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" tool_arch="x86_64" tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" tool_install_directory="$install_path/$tool_name/$version" tool_file_path="$tool_install_directory/$tool_name_moniker/bin/$tool_name" shim_path="$install_path/$tool_name.sh" uri="${base_uri}/$tool_folder/$tool_name/$tool_name_moniker.tar.gz" # Clean up tool and installers if [[ $clean = true ]]; then echo "Cleaning $tool_install_directory" if [[ -d $tool_install_directory ]]; then rm -rf $tool_install_directory fi echo "Cleaning $shim_path" if [[ -f $shim_path ]]; then rm -rf $shim_path fi tool_temp_path=$(GetTempPathFileName $uri) echo "Cleaning $tool_temp_path" if [[ -f $tool_temp_path ]]; then rm -rf $tool_temp_path fi exit 0 fi # Install tool if [[ -f $tool_file_path ]] && [[ $force = false ]]; then echo "$tool_name ($version) already exists, skipping install" exit 0 fi DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' exit 1 fi # Generate Shim # Always rewrite shims so that we are referencing the expected version NewScriptShim $shim_path $tool_file_path true if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' exit 1 fi exit 0 ================================================ FILE: eng/common/native/install-cmake.sh ================================================ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . $scriptroot/common-library.sh base_uri= install_path= version= clean=false force=false download_retries=5 retry_wait_time_seconds=30 while (($# > 0)); do lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --baseuri) base_uri=$2 shift 2 ;; --installpath) install_path=$2 shift 2 ;; --version) version=$2 shift 2 ;; --clean) clean=true shift 1 ;; --force) force=true shift 1 ;; --downloadretries) download_retries=$2 shift 2 ;; --retrywaittimeseconds) retry_wait_time_seconds=$2 shift 2 ;; --help) echo "Common settings:" echo " --baseuri Base file directory or Url wrom which to acquire tool archives" echo " --installpath Base directory to install native tool to" echo " --clean Don't install the tool, just clean up the current install of the tool" echo " --force Force install of tools even if they previously exist" echo " --help Print help and exit" echo "" echo "Advanced settings:" echo " --downloadretries Total number of retry attempts" echo " --retrywaittimeseconds Wait time between retry attempts in seconds" echo "" exit 0 ;; esac done tool_name="cmake" tool_os=$(GetCurrentOS) tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" tool_arch="x86_64" tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" tool_install_directory="$install_path/$tool_name/$version" tool_file_path="$tool_install_directory/$tool_name_moniker/bin/$tool_name" shim_path="$install_path/$tool_name.sh" uri="${base_uri}/$tool_folder/$tool_name/$tool_name_moniker.tar.gz" # Clean up tool and installers if [[ $clean = true ]]; then echo "Cleaning $tool_install_directory" if [[ -d $tool_install_directory ]]; then rm -rf $tool_install_directory fi echo "Cleaning $shim_path" if [[ -f $shim_path ]]; then rm -rf $shim_path fi tool_temp_path=$(GetTempPathFileName $uri) echo "Cleaning $tool_temp_path" if [[ -f $tool_temp_path ]]; then rm -rf $tool_temp_path fi exit 0 fi # Install tool if [[ -f $tool_file_path ]] && [[ $force = false ]]; then echo "$tool_name ($version) already exists, skipping install" exit 0 fi DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' exit 1 fi # Generate Shim # Always rewrite shims so that we are referencing the expected version NewScriptShim $shim_path $tool_file_path true if [[ $? != 0 ]]; then Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' exit 1 fi exit 0 ================================================ FILE: eng/common/native/install-dependencies.sh ================================================ #!/bin/sh set -e # This is a simple script primarily used for CI to install necessary dependencies # # Usage: # # ./install-dependencies.sh os="$(echo "$1" | tr "[:upper:]" "[:lower:]")" if [ -z "$os" ]; then . "$(dirname "$0")"/init-os-and-arch.sh fi case "$os" in linux) if [ -e /etc/os-release ]; then . /etc/os-release fi if [ "$ID" = "debian" ] || [ "$ID_LIKE" = "debian" ]; then apt update apt install -y build-essential gettext locales cmake llvm clang lld lldb liblldb-dev libunwind8-dev libicu-dev liblttng-ust-dev \ libssl-dev libkrb5-dev pigz cpio ninja-build localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ] || [ "$ID" = "centos" ]; then pkg_mgr="$(command -v tdnf 2>/dev/null || command -v dnf)" $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build elif [ "$ID" = "amzn" ]; then dnf install -y cmake llvm lld lldb clang python libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build elif [ "$ID" = "alpine" ]; then apk add build-base cmake bash curl clang llvm llvm-dev lld lldb-dev krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio ninja else echo "Unsupported distro. distro: $ID" exit 1 fi ;; osx|maccatalyst|ios|iossimulator|tvos|tvossimulator) echo "Installed xcode version: $(xcode-select -p)" export HOMEBREW_NO_INSTALL_CLEANUP=1 export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 # Skip brew update for now, see https://github.com/actions/setup-python/issues/577 # brew update --preinstall brew bundle --no-upgrade --file=- < [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$True)] [string] $ToolName, [Parameter(Mandatory=$True)] [string] $InstallPath, [Parameter(Mandatory=$True)] [string] $BaseUri, [Parameter(Mandatory=$True)] [string] $Version, [string] $CommonLibraryDirectory = $PSScriptRoot, [switch] $Force = $False, [switch] $Clean = $False, [int] $DownloadRetries = 5, [int] $RetryWaitTimeInSeconds = 30 ) . $PSScriptRoot\..\pipeline-logging-functions.ps1 # Import common library modules Import-Module -Name (Join-Path $CommonLibraryDirectory "CommonLibrary.psm1") try { # Define verbose switch if undefined $Verbose = $VerbosePreference -Eq "Continue" $Arch = CommonLibrary\Get-MachineArchitecture $ToolOs = "win64" if($Arch -Eq "x32") { $ToolOs = "win32" } $ToolNameMoniker = "$ToolName-$Version-$ToolOs-$Arch" $ToolInstallDirectory = Join-Path $InstallPath "$ToolName\$Version\" $Uri = "$BaseUri/windows/$ToolName/$ToolNameMoniker.zip" $ShimPath = Join-Path $InstallPath "$ToolName.exe" if ($Clean) { Write-Host "Cleaning $ToolInstallDirectory" if (Test-Path $ToolInstallDirectory) { Remove-Item $ToolInstallDirectory -Force -Recurse } Write-Host "Cleaning $ShimPath" if (Test-Path $ShimPath) { Remove-Item $ShimPath -Force } $ToolTempPath = CommonLibrary\Get-TempPathFilename -Path $Uri Write-Host "Cleaning $ToolTempPath" if (Test-Path $ToolTempPath) { Remove-Item $ToolTempPath -Force } exit 0 } # Install tool if ((Test-Path $ToolInstallDirectory) -And (-Not $Force)) { Write-Verbose "$ToolName ($Version) already exists, skipping install" } else { $InstallStatus = CommonLibrary\DownloadAndExtract -Uri $Uri ` -InstallDirectory $ToolInstallDirectory ` -Force:$Force ` -DownloadRetries $DownloadRetries ` -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` -Verbose:$Verbose if ($InstallStatus -Eq $False) { Write-PipelineTelemetryError "Installation failed" -Category "NativeToolsetBootstrapping" exit 1 } } $ToolFilePath = Get-ChildItem $ToolInstallDirectory -Recurse -Filter "$ToolName.exe" | % { $_.FullName } if (@($ToolFilePath).Length -Gt 1) { Write-Error "There are multiple copies of $ToolName in $($ToolInstallDirectory): `n$(@($ToolFilePath | out-string))" exit 1 } elseif (@($ToolFilePath).Length -Lt 1) { Write-Host "$ToolName was not found in $ToolInstallDirectory." exit 1 } # Generate shim # Always rewrite shims so that we are referencing the expected version $GenerateShimStatus = CommonLibrary\New-ScriptShim -ShimName $ToolName ` -ShimDirectory $InstallPath ` -ToolFilePath "$ToolFilePath" ` -BaseUri $BaseUri ` -Force:$Force ` -Verbose:$Verbose if ($GenerateShimStatus -Eq $False) { Write-PipelineTelemetryError "Generate shim failed" -Category "NativeToolsetBootstrapping" return 1 } exit 0 } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category "NativeToolsetBootstrapping" -Message $_ exit 1 } ================================================ FILE: eng/common/pipeline-logging-functions.ps1 ================================================ # Source for this file was taken from https://github.com/microsoft/azure-pipelines-task-lib/blob/11c9439d4af17e6475d9fe058e6b2e03914d17e6/powershell/VstsTaskSdk/LoggingCommandFunctions.ps1 and modified. # NOTE: You should not be calling these method directly as they are likely to change. Instead you should be calling the Write-Pipeline* functions defined in tools.ps1 $script:loggingCommandPrefix = '##vso[' $script:loggingCommandEscapeMappings = @( # TODO: WHAT ABOUT "="? WHAT ABOUT "%"? New-Object psobject -Property @{ Token = ';' ; Replacement = '%3B' } New-Object psobject -Property @{ Token = "`r" ; Replacement = '%0D' } New-Object psobject -Property @{ Token = "`n" ; Replacement = '%0A' } New-Object psobject -Property @{ Token = "]" ; Replacement = '%5D' } ) # TODO: BUG: Escape % ??? # TODO: Add test to verify don't need to escape "=". # Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set function Write-PipelineTelemetryError { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Category, [Parameter(Mandatory = $true)] [string]$Message, [Parameter(Mandatory = $false)] [string]$Type = 'error', [string]$ErrCode, [string]$SourcePath, [string]$LineNumber, [string]$ColumnNumber, [switch]$AsOutput, [switch]$Force) $PSBoundParameters.Remove('Category') | Out-Null if ($Force -Or ((Test-Path variable:ci) -And $ci)) { $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message" } $PSBoundParameters.Remove('Message') | Out-Null $PSBoundParameters.Add('Message', $Message) Write-PipelineTaskError @PSBoundParameters } # Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set function Write-PipelineTaskError { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Message, [Parameter(Mandatory = $false)] [string]$Type = 'error', [string]$ErrCode, [string]$SourcePath, [string]$LineNumber, [string]$ColumnNumber, [switch]$AsOutput, [switch]$Force ) if (!$Force -And (-Not (Test-Path variable:ci) -Or !$ci)) { if ($Type -eq 'error') { Write-Host $Message -ForegroundColor Red return } elseif ($Type -eq 'warning') { Write-Host $Message -ForegroundColor Yellow return } } if (($Type -ne 'error') -and ($Type -ne 'warning')) { Write-Host $Message return } $PSBoundParameters.Remove('Force') | Out-Null if (-not $PSBoundParameters.ContainsKey('Type')) { $PSBoundParameters.Add('Type', 'error') } Write-LogIssue @PSBoundParameters } function Write-PipelineSetVariable { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Name, [string]$Value, [switch]$Secret, [switch]$AsOutput, [bool]$IsMultiJobVariable = $true) if ((Test-Path variable:ci) -And $ci) { Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $Value -Properties @{ 'variable' = $Name 'isSecret' = $Secret 'isOutput' = $IsMultiJobVariable } -AsOutput:$AsOutput } } function Write-PipelinePrependPath { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Path, [switch]$AsOutput) if ((Test-Path variable:ci) -And $ci) { Write-LoggingCommand -Area 'task' -Event 'prependpath' -Data $Path -AsOutput:$AsOutput } } function Write-PipelineSetResult { [CmdletBinding()] param( [ValidateSet("Succeeded", "SucceededWithIssues", "Failed", "Cancelled", "Skipped")] [Parameter(Mandatory = $true)] [string]$Result, [string]$Message) if ((Test-Path variable:ci) -And $ci) { Write-LoggingCommand -Area 'task' -Event 'complete' -Data $Message -Properties @{ 'result' = $Result } } } <######################################## # Private functions. ########################################> function Format-LoggingCommandData { [CmdletBinding()] param([string]$Value, [switch]$Reverse) if (!$Value) { return '' } if (!$Reverse) { foreach ($mapping in $script:loggingCommandEscapeMappings) { $Value = $Value.Replace($mapping.Token, $mapping.Replacement) } } else { for ($i = $script:loggingCommandEscapeMappings.Length - 1 ; $i -ge 0 ; $i--) { $mapping = $script:loggingCommandEscapeMappings[$i] $Value = $Value.Replace($mapping.Replacement, $mapping.Token) } } return $Value } function Format-LoggingCommand { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Area, [Parameter(Mandatory = $true)] [string]$Event, [string]$Data, [hashtable]$Properties) # Append the preamble. [System.Text.StringBuilder]$sb = New-Object -TypeName System.Text.StringBuilder $null = $sb.Append($script:loggingCommandPrefix).Append($Area).Append('.').Append($Event) # Append the properties. if ($Properties) { $first = $true foreach ($key in $Properties.Keys) { [string]$value = Format-LoggingCommandData $Properties[$key] if ($value) { if ($first) { $null = $sb.Append(' ') $first = $false } else { $null = $sb.Append(';') } $null = $sb.Append("$key=$value") } } } # Append the tail and output the value. $Data = Format-LoggingCommandData $Data $sb.Append(']').Append($Data).ToString() } function Write-LoggingCommand { [CmdletBinding(DefaultParameterSetName = 'Parameters')] param( [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] [string]$Area, [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] [string]$Event, [Parameter(ParameterSetName = 'Parameters')] [string]$Data, [Parameter(ParameterSetName = 'Parameters')] [hashtable]$Properties, [Parameter(Mandatory = $true, ParameterSetName = 'Object')] $Command, [switch]$AsOutput) if ($PSCmdlet.ParameterSetName -eq 'Object') { Write-LoggingCommand -Area $Command.Area -Event $Command.Event -Data $Command.Data -Properties $Command.Properties -AsOutput:$AsOutput return } $command = Format-LoggingCommand -Area $Area -Event $Event -Data $Data -Properties $Properties if ($AsOutput) { $command } else { Write-Host $command } } function Write-LogIssue { [CmdletBinding()] param( [ValidateSet('warning', 'error')] [Parameter(Mandatory = $true)] [string]$Type, [string]$Message, [string]$ErrCode, [string]$SourcePath, [string]$LineNumber, [string]$ColumnNumber, [switch]$AsOutput) $command = Format-LoggingCommand -Area 'task' -Event 'logissue' -Data $Message -Properties @{ 'type' = $Type 'code' = $ErrCode 'sourcepath' = $SourcePath 'linenumber' = $LineNumber 'columnnumber' = $ColumnNumber } if ($AsOutput) { return $command } if ($Type -eq 'error') { $foregroundColor = $host.PrivateData.ErrorForegroundColor $backgroundColor = $host.PrivateData.ErrorBackgroundColor if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { $foregroundColor = [System.ConsoleColor]::Red $backgroundColor = [System.ConsoleColor]::Black } } else { $foregroundColor = $host.PrivateData.WarningForegroundColor $backgroundColor = $host.PrivateData.WarningBackgroundColor if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { $foregroundColor = [System.ConsoleColor]::Yellow $backgroundColor = [System.ConsoleColor]::Black } } Write-Host $command -ForegroundColor $foregroundColor -BackgroundColor $backgroundColor } ================================================ FILE: eng/common/pipeline-logging-functions.sh ================================================ #!/usr/bin/env bash function Write-PipelineTelemetryError { local telemetry_category='' local force=false local function_args=() local message='' while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -category|-c) telemetry_category=$2 shift ;; -force|-f) force=true ;; -*) function_args+=("$1 $2") shift ;; *) message=$* ;; esac shift done if [[ $force != true ]] && [[ "$ci" != true ]]; then echo "$message" >&2 return fi if [[ $force == true ]]; then function_args+=("-force") fi message="(NETCORE_ENGINEERING_TELEMETRY=$telemetry_category) $message" function_args+=("$message") Write-PipelineTaskError ${function_args[@]} } function Write-PipelineTaskError { local message_type="error" local sourcepath='' local linenumber='' local columnnumber='' local error_code='' local force=false while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -type|-t) message_type=$2 shift ;; -sourcepath|-s) sourcepath=$2 shift ;; -linenumber|-ln) linenumber=$2 shift ;; -columnnumber|-cn) columnnumber=$2 shift ;; -errcode|-e) error_code=$2 shift ;; -force|-f) force=true ;; *) break ;; esac shift done if [[ $force != true ]] && [[ "$ci" != true ]]; then echo "$@" >&2 return fi local message="##vso[task.logissue" message="$message type=$message_type" if [ -n "$sourcepath" ]; then message="$message;sourcepath=$sourcepath" fi if [ -n "$linenumber" ]; then message="$message;linenumber=$linenumber" fi if [ -n "$columnnumber" ]; then message="$message;columnnumber=$columnnumber" fi if [ -n "$error_code" ]; then message="$message;code=$error_code" fi message="$message]$*" echo "$message" } function Write-PipelineSetVariable { if [[ "$ci" != true ]]; then return fi local name='' local value='' local secret=false local as_output=false local is_multi_job_variable=true while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -name|-n) name=$2 shift ;; -value|-v) value=$2 shift ;; -secret|-s) secret=true ;; -as_output|-a) as_output=true ;; -is_multi_job_variable|-i) is_multi_job_variable=$2 shift ;; esac shift done value=${value/;/%3B} value=${value/\\r/%0D} value=${value/\\n/%0A} value=${value/]/%5D} local message="##vso[task.setvariable variable=$name;isSecret=$secret;isOutput=$is_multi_job_variable]$value" if [[ "$as_output" == true ]]; then $message else echo "$message" fi } function Write-PipelinePrependPath { local prepend_path='' while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -path|-p) prepend_path=$2 shift ;; esac shift done export PATH="$prepend_path:$PATH" if [[ "$ci" == true ]]; then echo "##vso[task.prependpath]$prepend_path" fi } function Write-PipelineSetResult { local result='' local message='' while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -result|-r) result=$2 shift ;; -message|-m) message=$2 shift ;; esac shift done if [[ "$ci" == true ]]; then echo "##vso[task.complete result=$result;]$message" fi } ================================================ FILE: eng/common/post-build/check-channel-consistency.ps1 ================================================ param( [Parameter(Mandatory=$true)][string] $PromoteToChannels, # List of channels that the build should be promoted to [Parameter(Mandatory=$true)][array] $AvailableChannelIds # List of channel IDs available in the YAML implementation ) try { $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true $disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 if ($PromoteToChannels -eq "") { Write-PipelineTaskError -Type 'warning' -Message "This build won't publish assets as it's not configured to any Maestro channel. If that wasn't intended use Darc to configure a default channel using add-default-channel for this branch or to promote it to a channel using add-build-to-channel. See https://github.com/dotnet/arcade/blob/main/Documentation/Darc.md#assigning-an-individual-build-to-a-channel for more info." ExitWithExitCode 0 } # Check that every channel that Maestro told to promote the build to # is available in YAML $PromoteToChannelsIds = $PromoteToChannels -split "\D" | Where-Object { $_ } $hasErrors = $false foreach ($id in $PromoteToChannelsIds) { if (($id -ne 0) -and ($id -notin $AvailableChannelIds)) { Write-PipelineTaskError -Message "Channel $id is not present in the post-build YAML configuration! This is an error scenario. Please contact @dnceng." $hasErrors = $true } } # The `Write-PipelineTaskError` doesn't error the script and we might report several errors # in the previous lines. The check below makes sure that we return an error state from the # script if we reported any validation error if ($hasErrors) { ExitWithExitCode 1 } Write-Host 'done.' } catch { Write-Host $_ Write-PipelineTelemetryError -Category 'CheckChannelConsistency' -Message "There was an error while trying to check consistency of Maestro default channels for the build and post-build YAML configuration." ExitWithExitCode 1 } ================================================ FILE: eng/common/post-build/nuget-validation.ps1 ================================================ # This script validates NuGet package metadata information using this # tool: https://github.com/NuGet/NuGetGallery/tree/jver-verify/src/VerifyMicrosoftPackage param( [Parameter(Mandatory=$true)][string] $PackagesPath # Path to where the packages to be validated are ) # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true $disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 try { & $PSScriptRoot\nuget-verification.ps1 ${PackagesPath}\*.nupkg } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'NuGetValidation' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/post-build/nuget-verification.ps1 ================================================ <# .SYNOPSIS Verifies that Microsoft NuGet packages have proper metadata. .DESCRIPTION Downloads a verification tool and runs metadata validation on the provided NuGet packages. This script writes an error if any of the provided packages fail validation. All arguments provided to this PowerShell script that do not match PowerShell parameters are passed on to the verification tool downloaded during the execution of this script. .PARAMETER NuGetExePath The path to the nuget.exe binary to use. If not provided, nuget.exe will be downloaded into the -DownloadPath directory. .PARAMETER PackageSource The package source to use to download the verification tool. If not provided, nuget.org will be used. .PARAMETER DownloadPath The directory path to download the verification tool and nuget.exe to. If not provided, %TEMP%\NuGet.VerifyNuGetPackage will be used. .PARAMETER args Arguments that will be passed to the verification tool. .EXAMPLE PS> .\verify.ps1 *.nupkg Verifies the metadata of all .nupkg files in the currect working directory. .EXAMPLE PS> .\verify.ps1 --help Displays the help text of the downloaded verifiction tool. .LINK https://github.com/NuGet/NuGetGallery/blob/master/src/VerifyMicrosoftPackage/README.md #> # This script was copied from https://github.com/NuGet/NuGetGallery/blob/3e25ad135146676bcab0050a516939d9958bfa5d/src/VerifyMicrosoftPackage/verify.ps1 [CmdletBinding(PositionalBinding = $false)] param( [string]$NuGetExePath, [string]$PackageSource = "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json", [string]$DownloadPath, [Parameter(ValueFromRemainingArguments = $true)] [string[]]$args ) # The URL to download nuget.exe. $nugetExeUrl = "https://dist.nuget.org/win-x86-commandline/v4.9.4/nuget.exe" # The package ID of the verification tool. $packageId = "NuGet.VerifyMicrosoftPackage" # The location that nuget.exe and the verification tool will be downloaded to. if (!$DownloadPath) { $DownloadPath = (Join-Path $env:TEMP "NuGet.VerifyMicrosoftPackage") } $fence = New-Object -TypeName string -ArgumentList '=', 80 # Create the download directory, if it doesn't already exist. if (!(Test-Path $DownloadPath)) { New-Item -ItemType Directory $DownloadPath | Out-Null } Write-Host "Using download path: $DownloadPath" if ($NuGetExePath) { $nuget = $NuGetExePath } else { $downloadedNuGetExe = Join-Path $DownloadPath "nuget.exe" # Download nuget.exe, if it doesn't already exist. if (!(Test-Path $downloadedNuGetExe)) { Write-Host "Downloading nuget.exe from $nugetExeUrl..." $ProgressPreference = 'SilentlyContinue' try { Invoke-WebRequest $nugetExeUrl -UseBasicParsing -OutFile $downloadedNuGetExe $ProgressPreference = 'Continue' } catch { $ProgressPreference = 'Continue' Write-Error $_ Write-Error "nuget.exe failed to download." exit } } $nuget = $downloadedNuGetExe } Write-Host "Using nuget.exe path: $nuget" Write-Host " " # Download the latest version of the verification tool. Write-Host "Downloading the latest version of $packageId from $packageSource..." Write-Host $fence & $nuget install $packageId ` -Prerelease ` -OutputDirectory $DownloadPath ` -Source $PackageSource Write-Host $fence Write-Host " " if ($LASTEXITCODE -ne 0) { Write-Error "nuget.exe failed to fetch the verify tool." exit } # Find the most recently downloaded tool Write-Host "Finding the most recently downloaded verification tool." $verifyProbePath = Join-Path $DownloadPath "$packageId.*" $verifyPath = Get-ChildItem -Path $verifyProbePath -Directory ` | Sort-Object -Property LastWriteTime -Descending ` | Select-Object -First 1 $verify = Join-Path $verifyPath "tools\NuGet.VerifyMicrosoftPackage.exe" Write-Host "Using verification tool: $verify" Write-Host " " # Execute the verification tool. Write-Host "Executing the verify tool..." Write-Host $fence & $verify $args Write-Host $fence Write-Host " " # Respond to the exit code. if ($LASTEXITCODE -ne 0) { Write-Error "The verify tool found some problems." } else { Write-Output "The verify tool succeeded." } ================================================ FILE: eng/common/post-build/publish-using-darc.ps1 ================================================ param( [Parameter(Mandatory=$true)][int] $BuildId, [Parameter(Mandatory=$true)][int] $PublishingInfraVersion, [Parameter(Mandatory=$true)][string] $AzdoToken, [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro.dot.net', [Parameter(Mandatory=$true)][string] $WaitPublishingFinish, [Parameter(Mandatory=$false)][string] $ArtifactsPublishingAdditionalParameters, [Parameter(Mandatory=$false)][string] $SymbolPublishingAdditionalParameters, [Parameter(Mandatory=$false)][string] $RequireDefaultChannels, [Parameter(Mandatory=$false)][string] $SkipAssetsPublishing, [Parameter(Mandatory=$false)][string] $runtimeSourceFeed, [Parameter(Mandatory=$false)][string] $runtimeSourceFeedKey ) try { # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true $disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 $darc = Get-Darc $optionalParams = [System.Collections.ArrayList]::new() if ("" -ne $ArtifactsPublishingAdditionalParameters) { $optionalParams.Add("--artifact-publishing-parameters") | Out-Null $optionalParams.Add($ArtifactsPublishingAdditionalParameters) | Out-Null } if ("" -ne $SymbolPublishingAdditionalParameters) { $optionalParams.Add("--symbol-publishing-parameters") | Out-Null $optionalParams.Add($SymbolPublishingAdditionalParameters) | Out-Null } if ("false" -eq $WaitPublishingFinish) { $optionalParams.Add("--no-wait") | Out-Null } if ("true" -eq $RequireDefaultChannels) { $optionalParams.Add("--default-channels-required") | Out-Null } if ("true" -eq $SkipAssetsPublishing) { $optionalParams.Add("--skip-assets-publishing") | Out-Null } & $darc add-build-to-channel ` --id $buildId ` --publishing-infra-version $PublishingInfraVersion ` --default-channels ` --source-branch main ` --azdev-pat "$AzdoToken" ` --bar-uri "$MaestroApiEndPoint" ` --ci ` --verbose ` @optionalParams if ($LastExitCode -ne 0) { Write-Host "Problems using Darc to promote build ${buildId} to default channels. Stopping execution..." exit 1 } Write-Host 'done.' } catch { Write-Host $_ Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to publish build '$BuildId' to default channels." ExitWithExitCode 1 } ================================================ FILE: eng/common/post-build/redact-logs.ps1 ================================================ [CmdletBinding(PositionalBinding=$False)] param( [Parameter(Mandatory=$true, Position=0)][string] $InputPath, [Parameter(Mandatory=$true)][string] $BinlogToolVersion, [Parameter(Mandatory=$false)][string] $DotnetPath, [Parameter(Mandatory=$false)][string] $PackageFeed = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json', # File with strings to redact - separated by newlines. # For comments start the line with '# ' - such lines are ignored [Parameter(Mandatory=$false)][string] $TokensFilePath, [Parameter(ValueFromRemainingArguments=$true)][String[]]$TokensToRedact, [Parameter(Mandatory=$false)][string] $runtimeSourceFeed, [Parameter(Mandatory=$false)][string] $runtimeSourceFeedKey ) try { $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true $disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 $packageName = 'binlogtool' $dotnet = $DotnetPath if (!$dotnet) { $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" } $toolList = & "$dotnet" tool list -g if ($toolList -like "*$packageName*") { & "$dotnet" tool uninstall $packageName -g } $toolPath = "$PSScriptRoot\..\..\..\.tools" $verbosity = 'minimal' New-Item -ItemType Directory -Force -Path $toolPath Push-Location -Path $toolPath try { Write-Host "Installing Binlog redactor CLI..." Write-Host "'$dotnet' new tool-manifest" & "$dotnet" new tool-manifest Write-Host "'$dotnet' tool install $packageName --local --source '$PackageFeed' -v $verbosity --version $BinlogToolVersion" & "$dotnet" tool install $packageName --local --source "$PackageFeed" -v $verbosity --version $BinlogToolVersion if (Test-Path $TokensFilePath) { Write-Host "Adding additional sensitive data for redaction from file: " $TokensFilePath $TokensToRedact += Get-Content -Path $TokensFilePath | Foreach {$_.Trim()} | Where { $_ -notmatch "^# " } } $optionalParams = [System.Collections.ArrayList]::new() Foreach ($p in $TokensToRedact) { if($p -match '^\$\(.*\)$') { Write-Host ("Ignoring token {0} as it is probably unexpanded AzDO variable" -f $p) } elseif($p) { $optionalParams.Add("-p:" + $p) | Out-Null } } & $dotnet binlogtool redact --input:$InputPath --recurse --in-place ` @optionalParams if ($LastExitCode -ne 0) { Write-PipelineTelemetryError -Category 'Redactor' -Type 'warning' -Message "Problems using Redactor tool (exit code: $LastExitCode). But ignoring them now." } } finally { Pop-Location } Write-Host 'done.' } catch { Write-Host $_ Write-PipelineTelemetryError -Category 'Redactor' -Message "There was an error while trying to redact logs. Error: $_" ExitWithExitCode 1 } ================================================ FILE: eng/common/post-build/symbols-validation.ps1 ================================================ param( [Parameter(Mandatory = $true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored [Parameter(Mandatory = $true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation [Parameter(Mandatory = $true)][string] $DotnetSymbolVersion, # Version of dotnet symbol to use [Parameter(Mandatory = $false)][switch] $CheckForWindowsPdbs, # If we should check for the existence of windows pdbs in addition to portable PDBs [Parameter(Mandatory = $false)][switch] $ContinueOnError, # If we should keep checking symbols after an error [Parameter(Mandatory = $false)][switch] $Clean, # Clean extracted symbols directory after checking symbols [Parameter(Mandatory = $false)][string] $SymbolExclusionFile # Exclude the symbols in the file from publishing to symbol server ) . $PSScriptRoot\..\tools.ps1 # Maximum number of jobs to run in parallel $MaxParallelJobs = 16 # Max number of retries $MaxRetry = 5 # Wait time between check for system load $SecondsBetweenLoadChecks = 10 # Set error codes Set-Variable -Name "ERROR_BADEXTRACT" -Option Constant -Value -1 Set-Variable -Name "ERROR_FILEDOESNOTEXIST" -Option Constant -Value -2 $WindowsPdbVerificationParam = "" if ($CheckForWindowsPdbs) { $WindowsPdbVerificationParam = "--windows-pdbs" } $ExclusionSet = New-Object System.Collections.Generic.HashSet[string]; if (!$InputPath -or !(Test-Path $InputPath)){ Write-Host "No symbols to validate." ExitWithExitCode 0 } #Check if the path exists if ($SymbolExclusionFile -and (Test-Path $SymbolExclusionFile)){ [string[]]$Exclusions = Get-Content "$SymbolExclusionFile" $Exclusions | foreach { if($_ -and $_.Trim()){$ExclusionSet.Add($_)} } } else{ Write-Host "Symbol Exclusion file does not exists. No symbols to exclude." } $CountMissingSymbols = { param( [string] $PackagePath, # Path to a NuGet package [string] $WindowsPdbVerificationParam # If we should check for the existence of windows pdbs in addition to portable PDBs ) Add-Type -AssemblyName System.IO.Compression.FileSystem Write-Host "Validating $PackagePath " # Ensure input file exist if (!(Test-Path $PackagePath)) { Write-PipelineTaskError "Input file does not exist: $PackagePath" return [pscustomobject]@{ result = $using:ERROR_FILEDOESNOTEXIST packagePath = $PackagePath } } # Extensions for which we'll look for symbols $RelevantExtensions = @('.dll', '.exe', '.so', '.dylib') # How many files are missing symbol information $MissingSymbols = 0 $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) $PackageGuid = New-Guid $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageGuid $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath 'Symbols' try { [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) } catch { Write-Host "Something went wrong extracting $PackagePath" Write-Host $_ return [pscustomobject]@{ result = $using:ERROR_BADEXTRACT packagePath = $PackagePath } } Get-ChildItem -Recurse $ExtractPath | Where-Object { $RelevantExtensions -contains $_.Extension } | ForEach-Object { $FileName = $_.FullName if ($FileName -Match '\\ref\\') { Write-Host "`t Ignoring reference assembly file " $FileName return } $FirstMatchingSymbolDescriptionOrDefault = { param( [string] $FullPath, # Full path to the module that has to be checked [string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols [string] $WindowsPdbVerificationParam, # Parameter to pass to potential check for windows-pdbs. [string] $SymbolsPath ) $FileName = [System.IO.Path]::GetFileName($FullPath) $Extension = [System.IO.Path]::GetExtension($FullPath) # Those below are potential symbol files that the `dotnet symbol` might # return. Which one will be returned depend on the type of file we are # checking and which type of file was uploaded. # The file itself is returned $SymbolPath = $SymbolsPath + '\' + $FileName # PDB file for the module $PdbPath = $SymbolPath.Replace($Extension, '.pdb') # PDB file for R2R module (created by crossgen) $NGenPdb = $SymbolPath.Replace($Extension, '.ni.pdb') # DBG file for a .so library $SODbg = $SymbolPath.Replace($Extension, '.so.dbg') # DWARF file for a .dylib $DylibDwarf = $SymbolPath.Replace($Extension, '.dylib.dwarf') $dotnetSymbolExe = "$env:USERPROFILE\.dotnet\tools" $dotnetSymbolExe = Resolve-Path "$dotnetSymbolExe\dotnet-symbol.exe" $totalRetries = 0 while ($totalRetries -lt $using:MaxRetry) { # Save the output and get diagnostic output $output = & $dotnetSymbolExe --symbols --modules $WindowsPdbVerificationParam $TargetServerParam $FullPath -o $SymbolsPath --diagnostics | Out-String if ((Test-Path $PdbPath) -and (Test-path $SymbolPath)) { return 'Module and PDB for Module' } elseif ((Test-Path $NGenPdb) -and (Test-Path $PdbPath) -and (Test-Path $SymbolPath)) { return 'Dll, PDB and NGen PDB' } elseif ((Test-Path $SODbg) -and (Test-Path $SymbolPath)) { return 'So and DBG for SO' } elseif ((Test-Path $DylibDwarf) -and (Test-Path $SymbolPath)) { return 'Dylib and Dwarf for Dylib' } elseif (Test-Path $SymbolPath) { return 'Module' } else { $totalRetries++ } } return $null } $FileRelativePath = $FileName.Replace("$ExtractPath\", "") if (($($using:ExclusionSet) -ne $null) -and ($($using:ExclusionSet).Contains($FileRelativePath) -or ($($using:ExclusionSet).Contains($FileRelativePath.Replace("\", "/"))))){ Write-Host "Skipping $FileName from symbol validation" } else { $FileGuid = New-Guid $ExpandedSymbolsPath = Join-Path -Path $SymbolsPath -ChildPath $FileGuid $SymbolsOnMSDL = & $FirstMatchingSymbolDescriptionOrDefault ` -FullPath $FileName ` -TargetServerParam '--microsoft-symbol-server' ` -SymbolsPath "$ExpandedSymbolsPath-msdl" ` -WindowsPdbVerificationParam $WindowsPdbVerificationParam $SymbolsOnSymWeb = & $FirstMatchingSymbolDescriptionOrDefault ` -FullPath $FileName ` -TargetServerParam '--internal-server' ` -SymbolsPath "$ExpandedSymbolsPath-symweb" ` -WindowsPdbVerificationParam $WindowsPdbVerificationParam Write-Host -NoNewLine "`t Checking file " $FileName "... " if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { Write-Host "Symbols found on MSDL ($SymbolsOnMSDL) and SymWeb ($SymbolsOnSymWeb)" } else { $MissingSymbols++ if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { Write-Host 'No symbols found on MSDL or SymWeb!' } else { if ($SymbolsOnMSDL -eq $null) { Write-Host 'No symbols found on MSDL!' } else { Write-Host 'No symbols found on SymWeb!' } } } } } if ($using:Clean) { Remove-Item $ExtractPath -Recurse -Force } Pop-Location return [pscustomobject]@{ result = $MissingSymbols packagePath = $PackagePath } } function CheckJobResult( $result, $packagePath, [ref]$DupedSymbols, [ref]$TotalFailures) { if ($result -eq $ERROR_BADEXTRACT) { Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath has duplicated symbol files" $DupedSymbols.Value++ } elseif ($result -eq $ERROR_FILEDOESNOTEXIST) { Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath does not exist" $TotalFailures.Value++ } elseif ($result -gt '0') { Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Missing symbols for $result modules in the package $packagePath" $TotalFailures.Value++ } else { Write-Host "All symbols verified for package $packagePath" } } function CheckSymbolsAvailable { if (Test-Path $ExtractPath) { Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue } $TotalPackages = 0 $TotalFailures = 0 $DupedSymbols = 0 Get-ChildItem "$InputPath\*.nupkg" | ForEach-Object { $FileName = $_.Name $FullName = $_.FullName # These packages from Arcade-Services include some native libraries that # our current symbol uploader can't handle. Below is a workaround until # we get issue: https://github.com/dotnet/arcade/issues/2457 sorted. if ($FileName -Match 'Microsoft\.DotNet\.Darc\.') { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } elseif ($FileName -Match 'Microsoft\.DotNet\.Maestro\.Tasks\.') { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } $TotalPackages++ Start-Job -ScriptBlock $CountMissingSymbols -ArgumentList @($FullName,$WindowsPdbVerificationParam) | Out-Null $NumJobs = @(Get-Job -State 'Running').Count while ($NumJobs -ge $MaxParallelJobs) { Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again." sleep $SecondsBetweenLoadChecks $NumJobs = @(Get-Job -State 'Running').Count } foreach ($Job in @(Get-Job -State 'Completed')) { $jobResult = Wait-Job -Id $Job.Id | Receive-Job CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) Remove-Job -Id $Job.Id } Write-Host } foreach ($Job in @(Get-Job)) { $jobResult = Wait-Job -Id $Job.Id | Receive-Job CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) } if ($TotalFailures -gt 0 -or $DupedSymbols -gt 0) { if ($TotalFailures -gt 0) { Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Symbols missing for $TotalFailures/$TotalPackages packages" } if ($DupedSymbols -gt 0) { Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$DupedSymbols/$TotalPackages packages had duplicated symbol files and could not be extracted" } ExitWithExitCode 1 } else { Write-Host "All symbols validated!" } } function InstallDotnetSymbol { $dotnetSymbolPackageName = 'dotnet-symbol' $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" $toolList = & "$dotnet" tool list --global if (($toolList -like "*$dotnetSymbolPackageName*") -and ($toolList -like "*$dotnetSymbolVersion*")) { Write-Host "dotnet-symbol version $dotnetSymbolVersion is already installed." } else { Write-Host "Installing dotnet-symbol version $dotnetSymbolVersion..." Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' & "$dotnet" tool install $dotnetSymbolPackageName --version $dotnetSymbolVersion --verbosity "minimal" --global } } try { InstallDotnetSymbol foreach ($Job in @(Get-Job)) { Remove-Job -Id $Job.Id } CheckSymbolsAvailable } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'CheckSymbols' -Message $_ ExitWithExitCode 1 } ================================================ FILE: eng/common/renovate.env ================================================ # Renovate Global Configuration # https://docs.renovatebot.com/self-hosted-configuration/ # # NOTE: This file uses bash/shell format and is sourced via `. renovate.env`. # Values containing spaces or special characters must be quoted. # Author to use for git commits made by Renovate # https://docs.renovatebot.com/configuration-options/#gitauthor export RENOVATE_GIT_AUTHOR='.NET Renovate ' # Disable rate limiting for PR creation (0 = unlimited) # https://docs.renovatebot.com/presets-default/#prhourlylimitnone # https://docs.renovatebot.com/presets-default/#prconcurrentlimitnone export RENOVATE_PR_HOURLY_LIMIT=0 export RENOVATE_PR_CONCURRENT_LIMIT=0 # Skip the onboarding PR that Renovate normally creates for new repos # https://docs.renovatebot.com/config-overview/#onboarding export RENOVATE_ONBOARDING=false # Any Renovate config file in the cloned repository is ignored. Only # the Renovate config file from the repo where the pipeline is running # is used (yes, those are the same repo but the sources may be different). # https://docs.renovatebot.com/self-hosted-configuration/#requireconfig export RENOVATE_REQUIRE_CONFIG=ignored # Customize the PR body content. This removes some of the default # sections that aren't relevant in a self-hosted config. # https://docs.renovatebot.com/configuration-options/#prheader # https://docs.renovatebot.com/configuration-options/#prbodynotes # https://docs.renovatebot.com/configuration-options/#prbodytemplate export RENOVATE_PR_HEADER='## Automated Dependency Update' export RENOVATE_PR_BODY_NOTES='["This PR has been created automatically by the [.NET Renovate Bot](https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md) to update one or more dependencies in your repo. Please review the changes and merge the PR if everything looks good."]' export RENOVATE_PR_BODY_TEMPLATE='{{{header}}}{{{table}}}{{{warnings}}}{{{notes}}}{{{changelogs}}}' # Extend the global config with additional presets # https://docs.renovatebot.com/self-hosted-configuration/#globalextends # Disable the Dependency Dashboard issue that tracks all updates export RENOVATE_GLOBAL_EXTENDS='[":disableDependencyDashboard"]' # Allow all commands for post-upgrade commands. export RENOVATE_ALLOWED_COMMANDS='[".*"]' ================================================ FILE: eng/common/retain-build.ps1 ================================================ Param( [Parameter(Mandatory=$true)][int] $buildId, [Parameter(Mandatory=$true)][string] $azdoOrgUri, [Parameter(Mandatory=$true)][string] $azdoProject, [Parameter(Mandatory=$true)][string] $token ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 function Get-AzDOHeaders( [string] $token) { $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":${token}")) $headers = @{"Authorization"="Basic $base64AuthInfo"} return $headers } function Update-BuildRetention( [string] $azdoOrgUri, [string] $azdoProject, [int] $buildId, [string] $token) { $headers = Get-AzDOHeaders -token $token $requestBody = "{ `"keepForever`": `"true`" }" $requestUri = "${azdoOrgUri}/${azdoProject}/_apis/build/builds/${buildId}?api-version=6.0" write-Host "Attempting to retain build using the following URI: ${requestUri} ..." try { Invoke-RestMethod -Uri $requestUri -Method Patch -Body $requestBody -Header $headers -contentType "application/json" Write-Host "Updated retention settings for build ${buildId}." } catch { Write-Error "Failed to update retention settings for build: $_.Exception.Response.StatusDescription" exit 1 } } Update-BuildRetention -azdoOrgUri $azdoOrgUri -azdoProject $azdoProject -buildId $buildId -token $token exit 0 ================================================ FILE: eng/common/sdk-task.ps1 ================================================ [CmdletBinding(PositionalBinding=$false)] Param( [string] $configuration = 'Debug', [string] $task, [string] $verbosity = 'minimal', [string] $msbuildEngine = $null, [switch] $restore, [switch] $prepareMachine, [switch][Alias('nobl')]$excludeCIBinaryLog, [switch]$noWarnAsError, [switch] $help, [string] $runtimeSourceFeed = '', [string] $runtimeSourceFeedKey = '', [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties ) $ci = $true $binaryLog = if ($excludeCIBinaryLog) { $false } else { $true } $warnAsError = if ($noWarnAsError) { $false } else { $true } . $PSScriptRoot\tools.ps1 function Print-Usage() { Write-Host "Common settings:" Write-Host " -task Name of Arcade task (name of a project in toolset directory of the Arcade SDK package)" Write-Host " -restore Restore dependencies" Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]" Write-Host " -help Print help and exit" Write-Host "" Write-Host "Advanced settings:" Write-Host " -prepareMachine Prepare machine for CI run" Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." Write-Host " -excludeCIBinaryLog When running on CI, allow no binary log (short: -nobl)" Write-Host "" Write-Host "Command line arguments not listed above are passed thru to msbuild." } function Build([string]$target) { $logSuffix = if ($target -eq 'Execute') { '' } else { ".$target" } $log = Join-Path $LogDir "$task$logSuffix.binlog" $binaryLogArg = if ($binaryLog) { "/bl:$log" } else { "" } $outputPath = Join-Path $ToolsetDir "$task\" MSBuild $taskProject ` $binaryLogArg ` /t:$target ` /p:Configuration=$configuration ` /p:RepoRoot=$RepoRoot ` /p:BaseIntermediateOutputPath=$outputPath ` /v:$verbosity ` @properties } try { if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { Print-Usage exit 0 } if ($task -eq "") { Write-PipelineTelemetryError -Category 'Build' -Message "Missing required parameter '-task '" Print-Usage ExitWithExitCode 1 } if( $msbuildEngine -eq "vs") { # Ensure desktop MSBuild is available for sdk tasks. $global:_MSBuildExe = InitializeVisualStudioMSBuild } $taskProject = GetSdkTaskProject $task if (!(Test-Path $taskProject)) { Write-PipelineTelemetryError -Category 'Build' -Message "Unknown task: $task" ExitWithExitCode 1 } if ($restore) { Build 'Restore' } Build 'Execute' } catch { Write-Host $_.ScriptStackTrace Write-PipelineTelemetryError -Category 'Build' -Message $_ ExitWithExitCode 1 } ExitWithExitCode 0 ================================================ FILE: eng/common/sdk-task.sh ================================================ #!/usr/bin/env bash show_usage() { echo "Common settings:" echo " --task Name of Arcade task (name of a project in toolset directory of the Arcade SDK package)" echo " --restore Restore dependencies" echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]" echo " --help Print help and exit" echo "" echo "Advanced settings:" echo " --excludeCIBinarylog Don't output binary log (short: -nobl)" echo " --noWarnAsError Do not warn as error" echo "" echo "Command line arguments not listed above are passed thru to msbuild." } source="${BASH_SOURCE[0]}" # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" Build() { local target=$1 local log_suffix="" [[ "$target" != "Execute" ]] && log_suffix=".$target" local log="$log_dir/$task$log_suffix.binlog" local binaryLogArg="" [[ $binary_log == true ]] && binaryLogArg="/bl:$log" local output_path="$toolset_dir/$task/" MSBuild "$taskProject" \ $binaryLogArg \ /t:"$target" \ /p:Configuration="$configuration" \ /p:RepoRoot="$repo_root" \ /p:BaseIntermediateOutputPath="$output_path" \ /v:"$verbosity" \ $properties } binary_log=true configuration="Debug" verbosity="minimal" exclude_ci_binary_log=false restore=false help=false properties='' warnAsError=true while (($# > 0)); do lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" case $lowerI in --task) task=$2 shift 2 ;; --restore) restore=true shift 1 ;; --verbosity) verbosity=$2 shift 2 ;; --excludecibinarylog|--nobl) binary_log=false exclude_ci_binary_log=true shift 1 ;; --noWarnAsError) warnAsError=false shift 1 ;; --help) help=true shift 1 ;; *) properties="$properties $1" shift 1 ;; esac done ci=true if $help; then show_usage exit 0 fi . "$scriptroot/tools.sh" InitializeToolset if [[ -z "$task" ]]; then Write-PipelineTelemetryError -Category 'Task' -Name 'MissingTask' -Message "Missing required parameter '-task '" ExitWithExitCode 1 fi taskProject=$(GetSdkTaskProject "$task") if [[ ! -e "$taskProject" ]]; then Write-PipelineTelemetryError -Category 'Task' -Name 'UnknownTask' -Message "Unknown task: $task" ExitWithExitCode 1 fi if $restore; then Build "Restore" fi Build "Execute" ExitWithExitCode 0 ================================================ FILE: eng/common/template-guidance.md ================================================ # Overview Arcade provides templates for public (`/templates`) and 1ES pipeline templates (`/templates-official`) scenarios. Pipelines which are required to be managed by 1ES pipeline templates should reference `/templates-offical`, all other pipelines may reference `/templates`. ## How to use Basic guidance is: - 1ES Pipeline Template or 1ES Microbuild template runs should reference `eng/common/templates-official`. Any internal production-graded pipeline should use these templates. - All other runs should reference `eng/common/templates`. See [azure-pipelines.yml](../../azure-pipelines.yml) (templates-official example) or [azure-pipelines-pr.yml](../../azure-pipelines-pr.yml) (templates example) for examples. #### The `templateIs1ESManaged` parameter The `templateIs1ESManaged` is available on most templates and affects which of the variants is used for nested templates. See [Development Notes](#development-notes) below for more information on the `templateIs1ESManaged1 parameter. - For templates under `job/`, `jobs/`, `steps`, or `post-build/`, this parameter must be explicitly set. ## Multiple outputs 1ES pipeline templates impose a policy where every publish artifact execution results in additional security scans being injected into your pipeline. When using `templates-official/jobs/jobs.yml`, Arcade reduces the number of additional security injections by gathering all publishing outputs into the [Build.ArtifactStagingDirectory](https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services), and utilizing the [outputParentDirectory](https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/1es-pipeline-templates/features/outputs#multiple-outputs) feature of 1ES pipeline templates. When implementing your pipeline, if you ensure publish artifacts are located in the `$(Build.ArtifactStagingDirectory)`, and utilize the 1ES provided template context, then you can reduce the number of security scans for your pipeline. Example: ``` yaml # azure-pipelines.yml extends: template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate parameters: stages: - stage: build jobs: - template: /eng/common/templates-official/jobs/jobs.yml@self parameters: # 1ES makes use of outputs to reduce security task injection overhead templateContext: outputs: - output: pipelineArtifact displayName: 'Publish logs from source' continueOnError: true condition: always() targetPath: $(Build.ArtifactStagingDirectory)/artifacts/log artifactName: Logs jobs: - job: Windows steps: - script: echo "friendly neighborhood" > artifacts/marvel/spiderman.txt # copy build outputs to artifact staging directory for publishing - task: CopyFiles@2 displayName: Gather build output inputs: SourceFolder: '$(System.DefaultWorkingDirectory)/artifacts/marvel' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/marvel' ``` Note: Multiple outputs are ONLY applicable to 1ES PT publishing (only usable when referencing `templates-official`). ## Development notes **Folder / file structure** ``` text eng\common\ [templates || templates-official]\ job\ job.yml (shim + artifact publishing logic) onelocbuild.yml (shim) publish-build-assets.yml (shim) source-build.yml (shim) source-index-stage1.yml (shim) jobs\ jobs.yml (shim) source-build.yml (shim) post-build\ post-build.yml (shim) common-variabls.yml (shim) setup-maestro-vars.yml (shim) steps\ publish-build-artifacts.yml (logic) publish-pipeline-artifacts.yml (logic) component-governance.yml (shim) publish-logs.yml (shim) retain-build.yml (shim) send-to-helix.yml (shim) source-build.yml (shim) variables\ pool-providers.yml (logic + redirect) # templates/variables/pool-providers.yml will redirect to templates-official/variables/pool-providers.yml if you are running in the internal project core-templates\ job\ job.yml (logic) onelocbuild.yml (logic) publish-build-assets.yml (logic) source-build.yml (logic) source-index-stage1.yml (logic) jobs\ jobs.yml (logic) source-build.yml (logic) post-build\ common-variabls.yml (logic) post-build.yml (logic) setup-maestro-vars.yml (logic) steps\ component-governance.yml (logic) publish-build-artifacts.yml (redirect) publish-logs.yml (logic) publish-pipeline-artifacts.yml (redirect) retain-build.yml (logic) send-to-helix.yml (logic) source-build.yml (logic) variables\ pool-providers.yml (redirect) ``` In the table above, a file is designated as "shim", "logic", or "redirect". - shim - represents a yaml file which is an intermediate step between pipeline logic and .Net Core Engineering's templates (`core-templates`) and defines the `is1ESPipeline` parameter value. - logic - represents actual base template logic. - redirect- represents a file in `core-templates` which redirects to the "logic" file in either `templates` or `templates-official`. Logic for Arcade's templates live **primarily** in the `core-templates` folder. The exceptions to the location of the logic files are around artifact publishing, which is handled differently between 1es pipeline templates and standard templates. `templates` and `templates-official` provide shim entry points which redirect to `core-templates` while also defining the `is1ESPipeline` parameter. If a shim is referenced in `templates`, then `is1ESPipeline` is set to `false`. If a shim is referenced in `templates-official`, then `is1ESPipeline` is set to `true`. Within `templates` and `templates-official`, the templates at the "stages", and "jobs" / "job" level have been replaced with shims. Templates at the "steps" and "variables" level are typically too granular to be replaced with shims and instead persist logic which is directly applicable to either scenario. Within `core-templates`, there are a handful of places where logic is dependent on which shim entry point was used. In those places, we redirect back to the respective logic file in `templates` or `templates-official`. ================================================ FILE: eng/common/templates/job/job.yml ================================================ parameters: enablePublishBuildArtifacts: false runAsPublic: false # CG related params, unused now and can eventually be removed disableComponentGovernance: unused # Sbom related params, unused now and can eventually be removed enableSbom: unused PackageVersion: unused BuildDropPath: unused jobs: - template: /eng/common/core-templates/job/job.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ if and(ne(parameter.key, 'steps'), ne(parameter.key, 'is1ESPipeline')) }}: ${{ parameter.key }}: ${{ parameter.value }} steps: - ${{ each step in parameters.steps }}: - ${{ step }} # we don't run CG in public - ${{ if eq(variables['System.TeamProject'], 'public') }}: - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" displayName: Set skipComponentGovernanceDetection variable artifactPublishSteps: - ${{ if ne(parameters.artifacts.publish, '') }}: - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: false args: displayName: Publish pipeline artifacts targetPath: '$(Build.ArtifactStagingDirectory)/artifacts' artifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} continueOnError: true condition: succeeded() retryCountOnTaskFailure: 10 # for any files being locked - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: false args: displayName: Publish pipeline artifacts targetPath: '$(Build.ArtifactStagingDirectory)/artifacts' artifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }}_Attempt$(System.JobAttempt) continueOnError: true condition: not(succeeded()) retryCountOnTaskFailure: 10 # for any files being locked - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: false args: targetPath: '$(Build.ArtifactStagingDirectory)/artifacts/log' artifactName: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} displayName: 'Publish logs' continueOnError: true condition: always() retryCountOnTaskFailure: 10 # for any files being locked - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: false args: displayName: Publish Logs targetPath: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' artifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)_Attempt$(System.JobAttempt)' ) }} continueOnError: true condition: always() retryCountOnTaskFailure: 10 # for any files being locked - ${{ if eq(parameters.enableBuildRetry, 'true') }}: - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: false args: targetPath: '$(System.DefaultWorkingDirectory)\eng\common\BuildConfiguration' artifactName: 'BuildConfiguration' displayName: 'Publish build retry configuration' continueOnError: true retryCountOnTaskFailure: 10 # for any files being locked ================================================ FILE: eng/common/templates/job/onelocbuild.yml ================================================ jobs: - template: /eng/common/core-templates/job/onelocbuild.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/job/publish-build-assets.yml ================================================ jobs: - template: /eng/common/core-templates/job/publish-build-assets.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/job/source-build.yml ================================================ jobs: - template: /eng/common/core-templates/job/source-build.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/job/source-index-stage1.yml ================================================ jobs: - template: /eng/common/core-templates/job/source-index-stage1.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/jobs/jobs.yml ================================================ jobs: - template: /eng/common/core-templates/jobs/jobs.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/jobs/source-build.yml ================================================ jobs: - template: /eng/common/core-templates/jobs/source-build.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/post-build/common-variables.yml ================================================ variables: - template: /eng/common/core-templates/post-build/common-variables.yml parameters: # Specifies whether to use 1ES is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/post-build/post-build.yml ================================================ stages: - template: /eng/common/core-templates/post-build/post-build.yml parameters: # Specifies whether to use 1ES is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/post-build/setup-maestro-vars.yml ================================================ steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: # Specifies whether to use 1ES is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/enable-internal-runtimes.yml ================================================ # Obtains internal runtime download credentials and populates the 'dotnetbuilds-internal-container-read-token-base64' # variable with the base64-encoded SAS token, by default steps: - template: /eng/common/core-templates/steps/enable-internal-runtimes.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/enable-internal-sources.yml ================================================ steps: - template: /eng/common/core-templates/steps/enable-internal-sources.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/generate-sbom.yml ================================================ steps: - template: /eng/common/core-templates/steps/generate-sbom.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/get-delegation-sas.yml ================================================ steps: - template: /eng/common/core-templates/steps/get-delegation-sas.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/get-federated-access-token.yml ================================================ steps: - template: /eng/common/core-templates/steps/get-federated-access-token.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/publish-build-artifacts.yml ================================================ parameters: - name: is1ESPipeline type: boolean default: false - name: displayName type: string default: 'Publish to Build Artifact' - name: condition type: string default: succeeded() - name: artifactName type: string - name: pathToPublish type: string - name: continueOnError type: boolean default: false - name: publishLocation type: string default: 'Container' - name: retryCountOnTaskFailure type: string default: 10 steps: - ${{ if eq(parameters.is1ESPipeline, true) }}: - 'eng/common/templates cannot be referenced from a 1ES managed template': error - task: PublishBuildArtifacts@1 displayName: ${{ parameters.displayName }} condition: ${{ parameters.condition }} ${{ if parameters.continueOnError }}: continueOnError: ${{ parameters.continueOnError }} inputs: PublishLocation: ${{ parameters.publishLocation }} PathtoPublish: ${{ parameters.pathToPublish }} ${{ if parameters.artifactName }}: ArtifactName: ${{ parameters.artifactName }} ${{ if parameters.retryCountOnTaskFailure }}: retryCountOnTaskFailure: ${{ parameters.retryCountOnTaskFailure }} ================================================ FILE: eng/common/templates/steps/publish-logs.yml ================================================ steps: - template: /eng/common/core-templates/steps/publish-logs.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/publish-pipeline-artifacts.yml ================================================ parameters: - name: is1ESPipeline type: boolean default: false - name: args type: object default: {} steps: - ${{ if eq(parameters.is1ESPipeline, true) }}: - 'eng/common/templates cannot be referenced from a 1ES managed template': error - task: PublishPipelineArtifact@1 displayName: ${{ coalesce(parameters.args.displayName, 'Publish to Build Artifact') }} ${{ if parameters.args.condition }}: condition: ${{ parameters.args.condition }} ${{ else }}: condition: succeeded() ${{ if parameters.args.continueOnError }}: continueOnError: ${{ parameters.args.continueOnError }} inputs: targetPath: ${{ parameters.args.targetPath }} ${{ if parameters.args.artifactName }}: artifactName: ${{ parameters.args.artifactName }} ${{ if parameters.args.publishLocation }}: publishLocation: ${{ parameters.args.publishLocation }} ${{ if parameters.args.fileSharePath }}: fileSharePath: ${{ parameters.args.fileSharePath }} ${{ if parameters.args.Parallel }}: parallel: ${{ parameters.args.Parallel }} ${{ if parameters.args.parallelCount }}: parallelCount: ${{ parameters.args.parallelCount }} ${{ if parameters.args.properties }}: properties: ${{ parameters.args.properties }} ================================================ FILE: eng/common/templates/steps/retain-build.yml ================================================ steps: - template: /eng/common/core-templates/steps/retain-build.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/send-to-helix.yml ================================================ steps: - template: /eng/common/core-templates/steps/send-to-helix.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/source-build.yml ================================================ steps: - template: /eng/common/core-templates/steps/source-build.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/source-index-stage1-publish.yml ================================================ steps: - template: /eng/common/core-templates/steps/source-index-stage1-publish.yml parameters: is1ESPipeline: false ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates/steps/vmr-sync.yml ================================================ ### These steps synchronize new code from product repositories into the VMR (https://github.com/dotnet/dotnet). ### They initialize the darc CLI and pull the new updates. ### Changes are applied locally onto the already cloned VMR (located in $vmrPath). parameters: - name: targetRef displayName: Target revision in dotnet/ to synchronize type: string default: $(Build.SourceVersion) - name: vmrPath displayName: Path where the dotnet/dotnet is checked out to type: string default: $(Agent.BuildDirectory)/vmr - name: additionalSyncs displayName: Optional list of package names whose repo's source will also be synchronized in the local VMR, e.g. NuGet.Protocol type: object default: [] steps: - checkout: vmr displayName: Clone dotnet/dotnet path: vmr clean: true - checkout: self displayName: Clone $(Build.Repository.Name) path: repo fetchDepth: 0 # This step is needed so that when we get a detached HEAD / shallow clone, # we still pull the commit into the temporary repo clone to use it during the sync. # Also unshallow the clone so that forwardflow command would work. - script: | git branch repo-head git rev-parse HEAD displayName: Label PR commit workingDirectory: $(Agent.BuildDirectory)/repo - script: | git config --global user.name "dotnet-maestro[bot]" git config --global user.email "dotnet-maestro[bot]@users.noreply.github.com" displayName: Set git author to dotnet-maestro[bot] workingDirectory: ${{ parameters.vmrPath }} - script: | ./eng/common/vmr-sync.sh \ --vmr ${{ parameters.vmrPath }} \ --tmp $(Agent.TempDirectory) \ --azdev-pat '$(dn-bot-all-orgs-code-r)' \ --ci \ --debug if [ "$?" -ne 0 ]; then echo "##vso[task.logissue type=error]Failed to synchronize the VMR" exit 1 fi displayName: Sync repo into VMR (Unix) condition: ne(variables['Agent.OS'], 'Windows_NT') workingDirectory: $(Agent.BuildDirectory)/repo - script: | git config --global diff.astextplain.textconv echo git config --system core.longpaths true displayName: Configure Windows git (longpaths, astextplain) condition: eq(variables['Agent.OS'], 'Windows_NT') - powershell: | ./eng/common/vmr-sync.ps1 ` -vmr ${{ parameters.vmrPath }} ` -tmp $(Agent.TempDirectory) ` -azdevPat '$(dn-bot-all-orgs-code-r)' ` -ci ` -debugOutput if ($LASTEXITCODE -ne 0) { echo "##vso[task.logissue type=error]Failed to synchronize the VMR" exit 1 } displayName: Sync repo into VMR (Windows) condition: eq(variables['Agent.OS'], 'Windows_NT') workingDirectory: $(Agent.BuildDirectory)/repo - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: - task: CopyFiles@2 displayName: Collect failed patches condition: failed() inputs: SourceFolder: '$(Agent.TempDirectory)' Contents: '*.patch' TargetFolder: '$(Build.ArtifactStagingDirectory)/FailedPatches' - publish: '$(Build.ArtifactStagingDirectory)/FailedPatches' artifact: $(System.JobDisplayName)_FailedPatches displayName: Upload failed patches condition: failed() - ${{ each assetName in parameters.additionalSyncs }}: # The vmr-sync script ends up staging files in the local VMR so we have to commit those - script: git commit --allow-empty -am "Forward-flow $(Build.Repository.Name)" displayName: Commit local VMR changes workingDirectory: ${{ parameters.vmrPath }} - script: | set -ex echo "Searching for details of asset ${{ assetName }}..." # Use darc to get dependencies information dependencies=$(./.dotnet/dotnet darc get-dependencies --name '${{ assetName }}' --ci) # Extract repository URL and commit hash repository=$(echo "$dependencies" | grep 'Repo:' | sed 's/Repo:[[:space:]]*//' | head -1) if [ -z "$repository" ]; then echo "##vso[task.logissue type=error]Asset ${{ assetName }} not found in the dependency list" exit 1 fi commit=$(echo "$dependencies" | grep 'Commit:' | sed 's/Commit:[[:space:]]*//' | head -1) echo "Updating the VMR from $repository / $commit..." cd .. git clone $repository ${{ assetName }} cd ${{ assetName }} git checkout $commit git branch "sync/$commit" ./eng/common/vmr-sync.sh \ --vmr ${{ parameters.vmrPath }} \ --tmp $(Agent.TempDirectory) \ --azdev-pat '$(dn-bot-all-orgs-code-r)' \ --ci \ --debug if [ "$?" -ne 0 ]; then echo "##vso[task.logissue type=error]Failed to synchronize the VMR" exit 1 fi displayName: Sync ${{ assetName }} into (Unix) condition: ne(variables['Agent.OS'], 'Windows_NT') workingDirectory: $(Agent.BuildDirectory)/repo - powershell: | $ErrorActionPreference = 'Stop' Write-Host "Searching for details of asset ${{ assetName }}..." $dependencies = .\.dotnet\dotnet darc get-dependencies --name '${{ assetName }}' --ci $repository = $dependencies | Select-String -Pattern 'Repo:\s+([^\s]+)' | Select-Object -First 1 $repository -match 'Repo:\s+([^\s]+)' | Out-Null $repository = $matches[1] if ($repository -eq $null) { Write-Error "Asset ${{ assetName }} not found in the dependency list" exit 1 } $commit = $dependencies | Select-String -Pattern 'Commit:\s+([^\s]+)' | Select-Object -First 1 $commit -match 'Commit:\s+([^\s]+)' | Out-Null $commit = $matches[1] Write-Host "Updating the VMR from $repository / $commit..." cd .. git clone $repository ${{ assetName }} cd ${{ assetName }} git checkout $commit git branch "sync/$commit" .\eng\common\vmr-sync.ps1 ` -vmr ${{ parameters.vmrPath }} ` -tmp $(Agent.TempDirectory) ` -azdevPat '$(dn-bot-all-orgs-code-r)' ` -ci ` -debugOutput if ($LASTEXITCODE -ne 0) { echo "##vso[task.logissue type=error]Failed to synchronize the VMR" exit 1 } displayName: Sync ${{ assetName }} into (Windows) condition: ne(variables['Agent.OS'], 'Windows_NT') workingDirectory: $(Agent.BuildDirectory)/repo ================================================ FILE: eng/common/templates/variables/pool-providers.yml ================================================ # Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, # otherwise it should go into the "normal" pools. This separates out the queueing and billing of released branches. # Motivation: # Once a given branch of a repository's output has been officially "shipped" once, it is then considered to be COGS # (Cost of goods sold) and should be moved to a servicing pool provider. This allows both separation of queueing # (allowing release builds and main PR builds to not intefere with each other) and billing (required for COGS. # Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services # team needs to move resources around and create new and potentially differently-named pools. Using this template # file from an Arcade-ified repo helps guard against both having to update one's release/* branches and renaming. # How to use: # This yaml assumes your shipped product branches use the naming convention "release/..." (which many do). # If we find alternate naming conventions in broad usage it can be added to the condition below. # # First, import the template in an arcade-ified repo to pick up the variables, e.g.: # # variables: # - template: /eng/common/templates/variables/pool-providers.yml # # ... then anywhere specifying the pool provider use the runtime variables, # $(DncEngInternalBuildPool) and $ (DncEngPublicBuildPool), e.g.: # # pool: # name: $(DncEngInternalBuildPool) # demands: ImageOverride -equals windows.vs2026.amd64 variables: - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - template: /eng/common/templates-official/variables/pool-providers.yml - ${{ else }}: # Coalesce the target and source branches so we know when a PR targets a release branch # If these variables are somehow missing, fall back to main (tends to have more capacity) # Any new -Svc alternative pools should have variables added here to allow for splitting work - name: DncEngPublicBuildPool value: $[ replace( replace( eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public' ) ] - name: DncEngInternalBuildPool value: $[ replace( replace( eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal' ), False, 'NetCore1ESPool-Internal' ) ] ================================================ FILE: eng/common/templates/vmr-build-pr.yml ================================================ # This pipeline is used for running the VMR verification of the PR changes in repo-level PRs. # # It will run a full set of verification jobs defined in: # https://github.com/dotnet/dotnet/blob/10060d128e3f470e77265f8490f5e4f72dae738e/eng/pipelines/templates/stages/vmr-build.yml#L27-L38 # # For repos that do not need to run the full set, you would do the following: # # 1. Copy this YML file to a repo-specific location, i.e. outside of eng/common. # # 2. Add `verifications` parameter to VMR template reference # # Examples: # - For source-build stage 1 verification, add the following: # verifications: [ "source-build-stage1" ] # # - For Windows only verifications, add the following: # verifications: [ "unified-build-windows-x64", "unified-build-windows-x86" ] trigger: none pr: none variables: - template: /eng/common/templates/variables/pool-providers.yml@self - name: skipComponentGovernanceDetection # we run CG on internal builds only value: true - name: Codeql.Enabled # we run CodeQL on internal builds only value: false resources: repositories: - repository: vmr type: github name: dotnet/dotnet endpoint: dotnet ref: refs/heads/main # Set to whatever VMR branch the PR build should insert into stages: - template: /eng/pipelines/templates/stages/vmr-build.yml@vmr parameters: isBuiltFromVmr: false scope: lite ================================================ FILE: eng/common/templates-official/job/job.yml ================================================ parameters: runAsPublic: false # Sbom related params, unused now and can eventually be removed enableSbom: unused PackageVersion: unused BuildDropPath: unused jobs: - template: /eng/common/core-templates/job/job.yml parameters: is1ESPipeline: true # publish artifacts # for 1ES managed templates, use the templateContext.output to handle multiple outputs. templateContext: outputParentDirectory: $(Build.ArtifactStagingDirectory) outputs: - ${{ if ne(parameters.artifacts.publish, '') }}: - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: - output: pipelineArtifact displayName: Publish pipeline artifacts targetPath: '$(Build.ArtifactStagingDirectory)/artifacts' artifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} condition: succeeded() retryCountOnTaskFailure: 10 # for any files being locked continueOnError: true - output: pipelineArtifact displayName: Publish pipeline artifacts targetPath: '$(Build.ArtifactStagingDirectory)/artifacts' artifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }}_Attempt$(System.JobAttempt) condition: not(succeeded()) retryCountOnTaskFailure: 10 # for any files being locked continueOnError: true - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: - output: pipelineArtifact targetPath: '$(Build.ArtifactStagingDirectory)/artifacts/log' artifactName: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)_Attempt$(System.JobAttempt)') }} displayName: 'Publish logs' continueOnError: true condition: always() retryCountOnTaskFailure: 10 # for any files being locked isProduction: false # logs are non-production artifacts - ${{ if eq(parameters.enablePublishBuildArtifacts, true) }}: - output: pipelineArtifact displayName: Publish Logs targetPath: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' artifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)_Attempt$(System.JobAttempt)' ) }} continueOnError: true condition: always() retryCountOnTaskFailure: 10 # for any files being locked isProduction: false # logs are non-production artifacts - ${{ if eq(parameters.enableBuildRetry, 'true') }}: - output: pipelineArtifact targetPath: '$(Build.ArtifactStagingDirectory)/artifacts/eng/common/BuildConfiguration' artifactName: 'BuildConfiguration' displayName: 'Publish build retry configuration' continueOnError: true retryCountOnTaskFailure: 10 # for any files being locked isProduction: false # BuildConfiguration is a non-production artifact # V4 publishing: automatically publish staged artifacts as a pipeline artifact. # The artifact name matches the SDK's FutureArtifactName ($(System.PhaseName)_Artifacts), # which is encoded in the asset manifest for downstream publishing to discover. # Jobs can opt in by setting enablePublishing: true. - ${{ if and(eq(parameters.publishingVersion, 4), eq(parameters.enablePublishing, 'true')) }}: - output: pipelineArtifact displayName: 'Publish V4 pipeline artifacts' targetPath: '$(Build.ArtifactStagingDirectory)/artifacts' artifactName: '$(System.PhaseName)_Artifacts' continueOnError: true retryCountOnTaskFailure: 10 # for any files being locked # add any outputs provided via root yaml - ${{ if ne(parameters.templateContext.outputs, '') }}: - ${{ each output in parameters.templateContext.outputs }}: - ${{ output }} # add any remaining templateContext properties ${{ each context in parameters.templateContext }}: ${{ if and(ne(context.key, 'outputParentDirectory'), ne(context.key, 'outputs')) }}: ${{ context.key }}: ${{ context.value }} ${{ each parameter in parameters }}: ${{ if and(ne(parameter.key, 'templateContext'), ne(parameter.key, 'is1ESPipeline')) }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/job/onelocbuild.yml ================================================ jobs: - template: /eng/common/core-templates/job/onelocbuild.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/job/publish-build-assets.yml ================================================ jobs: - template: /eng/common/core-templates/job/publish-build-assets.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/job/source-build.yml ================================================ jobs: - template: /eng/common/core-templates/job/source-build.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/job/source-index-stage1.yml ================================================ jobs: - template: /eng/common/core-templates/job/source-index-stage1.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/jobs/jobs.yml ================================================ jobs: - template: /eng/common/core-templates/jobs/jobs.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/jobs/source-build.yml ================================================ jobs: - template: /eng/common/core-templates/jobs/source-build.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/post-build/common-variables.yml ================================================ variables: - template: /eng/common/core-templates/post-build/common-variables.yml parameters: # Specifies whether to use 1ES is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/post-build/post-build.yml ================================================ stages: - template: /eng/common/core-templates/post-build/post-build.yml parameters: # Specifies whether to use 1ES is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/post-build/setup-maestro-vars.yml ================================================ steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: # Specifies whether to use 1ES is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/enable-internal-runtimes.yml ================================================ # Obtains internal runtime download credentials and populates the 'dotnetbuilds-internal-container-read-token-base64' # variable with the base64-encoded SAS token, by default steps: - template: /eng/common/core-templates/steps/enable-internal-runtimes.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/enable-internal-sources.yml ================================================ steps: - template: /eng/common/core-templates/steps/enable-internal-sources.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/generate-sbom.yml ================================================ steps: - template: /eng/common/core-templates/steps/generate-sbom.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/get-delegation-sas.yml ================================================ steps: - template: /eng/common/core-templates/steps/get-delegation-sas.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/get-federated-access-token.yml ================================================ steps: - template: /eng/common/core-templates/steps/get-federated-access-token.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/publish-build-artifacts.yml ================================================ parameters: - name: displayName type: string default: 'Publish to Build Artifact' - name: condition type: string default: succeeded() - name: artifactName type: string - name: pathToPublish type: string - name: continueOnError type: boolean default: false - name: publishLocation type: string default: 'Container' - name: is1ESPipeline type: boolean default: true - name: retryCountOnTaskFailure type: string default: 10 steps: - ${{ if ne(parameters.is1ESPipeline, true) }}: - 'eng/common/templates-official cannot be referenced from a non-1ES managed template': error - task: 1ES.PublishBuildArtifacts@1 displayName: ${{ parameters.displayName }} condition: ${{ parameters.condition }} ${{ if parameters.continueOnError }}: continueOnError: ${{ parameters.continueOnError }} inputs: PublishLocation: ${{ parameters.publishLocation }} PathtoPublish: ${{ parameters.pathToPublish }} ${{ if parameters.artifactName }}: ArtifactName: ${{ parameters.artifactName }} ${{ if parameters.retryCountOnTaskFailure }}: retryCountOnTaskFailure: ${{ parameters.retryCountOnTaskFailure }} ================================================ FILE: eng/common/templates-official/steps/publish-logs.yml ================================================ steps: - template: /eng/common/core-templates/steps/publish-logs.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/publish-pipeline-artifacts.yml ================================================ parameters: - name: is1ESPipeline type: boolean default: true - name: args type: object default: {} steps: - ${{ if ne(parameters.is1ESPipeline, true) }}: - 'eng/common/templates-official cannot be referenced from a non-1ES managed template': error - task: 1ES.PublishPipelineArtifact@1 displayName: ${{ coalesce(parameters.args.displayName, 'Publish to Build Artifact') }} ${{ if parameters.args.condition }}: condition: ${{ parameters.args.condition }} ${{ else }}: condition: succeeded() ${{ if parameters.args.continueOnError }}: continueOnError: ${{ parameters.args.continueOnError }} inputs: targetPath: ${{ parameters.args.targetPath }} ${{ if parameters.args.artifactName }}: artifactName: ${{ parameters.args.artifactName }} ${{ if parameters.args.properties }}: properties: ${{ parameters.args.properties }} ${{ if ne(parameters.args.sbomEnabled, '') }}: sbomEnabled: ${{ parameters.args.sbomEnabled }} ${{ if ne(parameters.args.isProduction, '') }}: isProduction: ${{ parameters.args.isProduction }} ================================================ FILE: eng/common/templates-official/steps/retain-build.yml ================================================ steps: - template: /eng/common/core-templates/steps/retain-build.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/send-to-helix.yml ================================================ steps: - template: /eng/common/core-templates/steps/send-to-helix.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/source-build.yml ================================================ steps: - template: /eng/common/core-templates/steps/source-build.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/steps/source-index-stage1-publish.yml ================================================ steps: - template: /eng/common/core-templates/steps/source-index-stage1-publish.yml parameters: is1ESPipeline: true ${{ each parameter in parameters }}: ${{ parameter.key }}: ${{ parameter.value }} ================================================ FILE: eng/common/templates-official/variables/pool-providers.yml ================================================ # Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, # otherwise it should go into the "normal" pools. This separates out the queueing and billing of released branches. # Motivation: # Once a given branch of a repository's output has been officially "shipped" once, it is then considered to be COGS # (Cost of goods sold) and should be moved to a servicing pool provider. This allows both separation of queueing # (allowing release builds and main PR builds to not intefere with each other) and billing (required for COGS. # Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services # team needs to move resources around and create new and potentially differently-named pools. Using this template # file from an Arcade-ified repo helps guard against both having to update one's release/* branches and renaming. # How to use: # This yaml assumes your shipped product branches use the naming convention "release/..." (which many do). # If we find alternate naming conventions in broad usage it can be added to the condition below. # # First, import the template in an arcade-ified repo to pick up the variables, e.g.: # # variables: # - template: /eng/common/templates-official/variables/pool-providers.yml # # ... then anywhere specifying the pool provider use the runtime variables, # $(DncEngInternalBuildPool) # # pool: # name: $(DncEngInternalBuildPool) # image: windows.vs2026.amd64 variables: # Coalesce the target and source branches so we know when a PR targets a release branch # If these variables are somehow missing, fall back to main (tends to have more capacity) # Any new -Svc alternative pools should have variables added here to allow for splitting work - name: DncEngInternalBuildPool value: $[ replace( replace( eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal' ), False, 'NetCore1ESPool-Internal' ) ] ================================================ FILE: eng/common/tools.ps1 ================================================ # Initialize variables if they aren't already defined. # These may be defined as parameters of the importing script, or set after importing this script. # CI mode - set to true on CI server for PR validation build or official build. [bool]$ci = if (Test-Path variable:ci) { $ci } else { $false } # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. [string]$configuration = if (Test-Path variable:configuration) { $configuration } else { 'Debug' } # Set to true to opt out of outputting binary log while running in CI [bool]$excludeCIBinarylog = if (Test-Path variable:excludeCIBinarylog) { $excludeCIBinarylog } else { $false } # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. [bool]$binaryLog = if (Test-Path variable:binaryLog) { $binaryLog } else { $ci -and !$excludeCIBinarylog } # Set to true to use the pipelines logger which will enable Azure logging output. # https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md # This flag is meant as a temporary opt-opt for the feature while validate it across # our consumers. It will be deleted in the future. [bool]$pipelinesLog = if (Test-Path variable:pipelinesLog) { $pipelinesLog } else { $ci } # Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes). [bool]$prepareMachine = if (Test-Path variable:prepareMachine) { $prepareMachine } else { $false } # True to restore toolsets and dependencies. [bool]$restore = if (Test-Path variable:restore) { $restore } else { $true } # Adjusts msbuild verbosity level. [string]$verbosity = if (Test-Path variable:verbosity) { $verbosity } else { 'minimal' } # Set to true to reuse msbuild nodes. Recommended to not reuse on CI. [bool]$nodeReuse = if (Test-Path variable:nodeReuse) { $nodeReuse } else { !$ci } # Configures warning treatment in msbuild. [bool]$warnAsError = if (Test-Path variable:warnAsError) { $warnAsError } else { $true } # Specifies semi-colon delimited list of warning codes that should not be treated as errors. [string]$warnNotAsError = if (Test-Path variable:warnNotAsError) { $warnNotAsError } else { '' } # Specifies which msbuild engine to use for build: 'vs', 'dotnet' or unspecified (determined based on presence of tools.vs in global.json). [string]$msbuildEngine = if (Test-Path variable:msbuildEngine) { $msbuildEngine } else { $null } # True to attempt using .NET Core already that meets requirements specified in global.json # installed on the machine instead of downloading one. [bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true } # Enable repos to use a particular version of the on-line dotnet-install scripts. # default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1 [string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' } # True to use global NuGet cache instead of restoring packages to repository-local directory. [bool]$useGlobalNuGetCache = if (Test-Path variable:useGlobalNuGetCache) { $useGlobalNuGetCache } else { !$ci } # True to exclude prerelease versions Visual Studio during build [bool]$excludePrereleaseVS = if (Test-Path variable:excludePrereleaseVS) { $excludePrereleaseVS } else { $false } # An array of names of processes to stop on script exit if prepareMachine is true. $processesToStopOnExit = if (Test-Path variable:processesToStopOnExit) { $processesToStopOnExit } else { @('msbuild', 'dotnet', 'vbcscompiler') } $disableConfigureToolsetImport = if (Test-Path variable:disableConfigureToolsetImport) { $disableConfigureToolsetImport } else { $null } set-strictmode -version 2.0 $ErrorActionPreference = 'Stop' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # If specifies, provides an alternate path for getting .NET Core SDKs and Runtimes. This script will still try public sources first. [string]$runtimeSourceFeed = if (Test-Path variable:runtimeSourceFeed) { $runtimeSourceFeed } else { $null } # Base-64 encoded SAS token that has permission to storage container described by $runtimeSourceFeed [string]$runtimeSourceFeedKey = if (Test-Path variable:runtimeSourceFeedKey) { $runtimeSourceFeedKey } else { $null } # True when the build is running within the VMR. [bool]$fromVMR = if (Test-Path variable:fromVMR) { $fromVMR } else { $false } function Create-Directory ([string[]] $path) { New-Item -Path $path -Force -ItemType 'Directory' | Out-Null } function Unzip([string]$zipfile, [string]$outpath) { Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath) } # This will exec a process using the console and return it's exit code. # This will not throw when the process fails. # Returns process exit code. function Exec-Process([string]$command, [string]$commandArgs) { $startInfo = New-Object System.Diagnostics.ProcessStartInfo $startInfo.FileName = $command $startInfo.Arguments = $commandArgs $startInfo.UseShellExecute = $false $startInfo.WorkingDirectory = Get-Location $process = New-Object System.Diagnostics.Process $process.StartInfo = $startInfo $process.Start() | Out-Null $finished = $false try { while (-not $process.WaitForExit(100)) { # Non-blocking loop done to allow ctr-c interrupts } $finished = $true return $global:LASTEXITCODE = $process.ExitCode } finally { # If we didn't finish then an error occurred or the user hit ctrl-c. Either # way kill the process if (-not $finished) { $process.Kill() } } } # Take the given block, print it, print what the block probably references from the current set of # variables using low-effort string matching, then run the block. # # This is intended to replace the pattern of manually copy-pasting a command, wrapping it in quotes, # and printing it using "Write-Host". The copy-paste method is more readable in build logs, but less # maintainable and less reliable. It is easy to make a mistake and modify the command without # properly updating the "Write-Host" line, resulting in misleading build logs. The probability of # this mistake makes the pattern hard to trust when it shows up in build logs. Finding the bug in # existing source code can also be difficult, because the strings are not aligned to each other and # the line may be 300+ columns long. # # By removing the need to maintain two copies of the command, Exec-BlockVerbosely avoids the issues. # # In Bash (or any posix-like shell), "set -x" prints usable verbose output automatically. # "Set-PSDebug" appears to be similar at first glance, but unfortunately, it isn't very useful: it # doesn't print any info about the variables being used by the command, which is normally the # interesting part to diagnose. function Exec-BlockVerbosely([scriptblock] $block) { Write-Host "--- Running script block:" $blockString = $block.ToString().Trim() Write-Host $blockString Write-Host "--- List of variables that might be used:" # For each variable x in the environment, check the block for a reference to x via simple "$x" or # "@x" syntax. This doesn't detect other ways to reference variables ("${x}" nor "$variable:x", # among others). It only catches what this function was originally written for: simple # command-line commands. $variableTable = Get-Variable | Where-Object { $blockString.Contains("`$$($_.Name)") -or $blockString.Contains("@$($_.Name)") } | Format-Table -AutoSize -HideTableHeaders -Wrap | Out-String Write-Host $variableTable.Trim() Write-Host "--- Executing:" & $block Write-Host "--- Done running script block!" } # createSdkLocationFile parameter enables a file being generated under the toolset directory # which writes the sdk's location into. This is only necessary for cmd --> powershell invocations # as dot sourcing isn't possible. function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { if (Test-Path variable:global:_DotNetInstallDir) { return $global:_DotNetInstallDir } # Disable first run since we do not need all ASP.NET packages restored. $env:DOTNET_NOLOGO=1 # Disable telemetry on CI. if ($ci) { $env:DOTNET_CLI_TELEMETRY_OPTOUT=1 } # Keep repo builds isolated from machine-installed SDK state and workload advertising. # This avoids preview SDK builds picking up mismatched workloads on CI images. $env:DOTNET_MULTILEVEL_LOOKUP = '0' $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' $env:DOTNET_CLI_WORKLOAD_UPDATE_NOTIFY_DISABLE = '1' # Find the first path on %PATH% that contains the dotnet.exe if ($useInstalledDotNetCli -and (-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -eq $null)) { $dotnetExecutable = GetExecutableFileName 'dotnet' $dotnetCmd = Get-Command $dotnetExecutable -ErrorAction SilentlyContinue if ($dotnetCmd -ne $null) { $env:DOTNET_INSTALL_DIR = Split-Path $dotnetCmd.Path -Parent } } $dotnetSdkVersion = $GlobalJson.tools.dotnet # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. if ((-not $globalJsonHasRuntimes) -and (-not [string]::IsNullOrEmpty($env:DOTNET_INSTALL_DIR)) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) { $dotnetRoot = $env:DOTNET_INSTALL_DIR } else { if (-not [string]::IsNullOrEmpty($env:DOTNET_GLOBAL_INSTALL_DIR)) { $dotnetRoot = $env:DOTNET_GLOBAL_INSTALL_DIR } else { $dotnetRoot = Join-Path $RepoRoot '.dotnet' } if (-not (Test-Path(Join-Path $dotnetRoot "sdk\$dotnetSdkVersion"))) { if ($install) { InstallDotNetSdk $dotnetRoot $dotnetSdkVersion } else { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" ExitWithExitCode 1 } } $env:DOTNET_INSTALL_DIR = $dotnetRoot } # Creates a temporary file under the toolset dir. # The following code block is protecting against concurrent access so that this function can # be called in parallel. if ($createSdkLocationFile) { do { $sdkCacheFileTemp = Join-Path $ToolsetDir $([System.IO.Path]::GetRandomFileName()) } until (!(Test-Path $sdkCacheFileTemp)) Set-Content -Path $sdkCacheFileTemp -Value $dotnetRoot try { Move-Item -Force $sdkCacheFileTemp (Join-Path $ToolsetDir 'sdk.txt') } catch { # Somebody beat us Remove-Item -Path $sdkCacheFileTemp } } # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom # build steps from using anything other than what we've downloaded. # It also ensures that VS msbuild will use the downloaded sdk targets. $env:PATH = "$dotnetRoot;$env:PATH" # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build Write-PipelinePrependPath -Path $dotnetRoot Write-PipelineSetVariable -Name 'DOTNET_NOLOGO' -Value '1' Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0' Write-PipelineSetVariable -Name 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' -Value '1' Write-PipelineSetVariable -Name 'DOTNET_CLI_WORKLOAD_UPDATE_NOTIFY_DISABLE' -Value '1' return $global:_DotNetInstallDir = $dotnetRoot } function Retry($downloadBlock, $maxRetries = 5) { $retries = 1 while($true) { try { & $downloadBlock break } catch { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ } if (++$retries -le $maxRetries) { $delayInSeconds = [math]::Pow(2, $retries) - 1 # Exponential backoff Write-Host "Retrying. Waiting for $delayInSeconds seconds before next attempt ($retries of $maxRetries)." Start-Sleep -Seconds $delayInSeconds } else { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to download file in $maxRetries attempts." break } } } function GetDotNetInstallScript([string] $dotnetRoot) { $installScript = Join-Path $dotnetRoot 'dotnet-install.ps1' $shouldDownload = $false if (!(Test-Path $installScript)) { $shouldDownload = $true } else { # Check if the script is older than 30 days $fileAge = (Get-Date) - (Get-Item $installScript).LastWriteTime if ($fileAge.Days -gt 30) { Write-Host "Existing install script is too old, re-downloading..." $shouldDownload = $true } } if ($shouldDownload) { Create-Directory $dotnetRoot $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit $uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" Retry({ Write-Host "GET $uri" Invoke-WebRequest $uri -UseBasicParsing -OutFile $installScript }) } return $installScript } function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = '', [switch] $noPath) { InstallDotNet $dotnetRoot $version $architecture '' $false $runtimeSourceFeed $runtimeSourceFeedKey -noPath:$noPath } function InstallDotNet([string] $dotnetRoot, [string] $version, [string] $architecture = '', [string] $runtime = '', [bool] $skipNonVersionedFiles = $false, [string] $runtimeSourceFeed = '', [string] $runtimeSourceFeedKey = '', [switch] $noPath) { $dotnetVersionLabel = "'sdk v$version'" # For performance this check is duplicated in src/Microsoft.DotNet.Arcade.Sdk/src/InstallDotNetCore.cs # if you are making changes here, consider if you need to make changes there as well. if ($runtime -ne '' -and $runtime -ne 'sdk') { $runtimePath = $dotnetRoot $runtimePath = $runtimePath + "\shared" if ($runtime -eq "dotnet") { $runtimePath = $runtimePath + "\Microsoft.NETCore.App" } if ($runtime -eq "aspnetcore") { $runtimePath = $runtimePath + "\Microsoft.AspNetCore.App" } if ($runtime -eq "windowsdesktop") { $runtimePath = $runtimePath + "\Microsoft.WindowsDesktop.App" } $runtimePath = $runtimePath + "\" + $version $dotnetVersionLabel = "runtime toolset '$runtime/$architecture v$version'" if (Test-Path $runtimePath) { Write-Host " Runtime toolset '$runtime/$architecture v$version' already installed." $installSuccess = $true Exit } } $installScript = GetDotNetInstallScript $dotnetRoot $installParameters = @{ Version = $version InstallDir = $dotnetRoot } if ($architecture) { $installParameters.Architecture = $architecture } if ($runtime) { $installParameters.Runtime = $runtime } if ($skipNonVersionedFiles) { $installParameters.SkipNonVersionedFiles = $skipNonVersionedFiles } if ($noPath) { $installParameters.NoPath = $True } $variations = @() $variations += @($installParameters) $dotnetBuilds = $installParameters.Clone() $dotnetbuilds.AzureFeed = "https://ci.dot.net/public" $variations += @($dotnetBuilds) if ($runtimeSourceFeed) { $runtimeSource = $installParameters.Clone() $runtimeSource.AzureFeed = $runtimeSourceFeed if ($runtimeSourceFeedKey) { $decodedBytes = [System.Convert]::FromBase64String($runtimeSourceFeedKey) $decodedString = [System.Text.Encoding]::UTF8.GetString($decodedBytes) $runtimeSource.FeedCredential = $decodedString } $variations += @($runtimeSource) } $installSuccess = $false foreach ($variation in $variations) { if ($variation | Get-Member AzureFeed) { $location = $variation.AzureFeed } else { $location = "public location"; } Write-Host " Attempting to install $dotnetVersionLabel from $location." try { & $installScript @variation $installSuccess = $true break } catch { Write-Host " Failed to install $dotnetVersionLabel from $location." } } if (-not $installSuccess) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install $dotnetVersionLabel from any of the specified locations." ExitWithExitCode 1 } } # # Locates Visual Studio MSBuild installation. # The preference order for MSBuild to use is as follows: # # 1. MSBuild from an active VS command prompt # 2. MSBuild from a compatible VS installation # # Returns full path to msbuild.exe. # Throws on failure. # function InitializeVisualStudioMSBuild([object]$vsRequirements = $null) { if (-not (IsWindowsPlatform)) { throw "Cannot initialize Visual Studio on non-Windows" } if (Test-Path variable:global:_MSBuildExe) { return $global:_MSBuildExe } # Minimum VS version to require. $vsMinVersionReqdStr = '18.0' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { $vsRequirements = $GlobalJson.tools.vs } else { $vsRequirements = New-Object PSObject -Property @{ version = $vsMinVersionReqdStr } } } $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { $vsMinVersionReqdStr } $vsMinVersion = [Version]::new($vsMinVersionStr) # Try msbuild command available in the environment. if ($env:VSINSTALLDIR -ne $null) { $msbuildCmd = Get-Command 'msbuild.exe' -ErrorAction SilentlyContinue if ($msbuildCmd -ne $null) { # Workaround for https://github.com/dotnet/roslyn/issues/35793 # Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+ $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0]) if ($msbuildVersion -ge $vsMinVersion) { return $global:_MSBuildExe = $msbuildCmd.Path } # Report error - the developer environment is initialized with incompatible VS version. throw "Developer Command Prompt for VS $($env:VisualStudioVersion) is not recent enough. Please upgrade to $vsMinVersionStr or build from a plain CMD window" } } # Locate Visual Studio installation. $vsInfo = LocateVisualStudio $vsRequirements if ($vsInfo -ne $null) { # Ensure vsInstallDir has a trailing slash $vsInstallDir = Join-Path $vsInfo.installationPath "\" $vsMajorVersion = $vsInfo.installationVersion.Split('.')[0] InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion } else { throw 'Unable to find Visual Studio that has required version and components installed' } $msbuildVersionDir = if ([int]$vsMajorVersion -lt 16) { "$vsMajorVersion.0" } else { "Current" } $local:BinFolder = Join-Path $vsInstallDir "MSBuild\$msbuildVersionDir\Bin" $local:Prefer64bit = if (Get-Member -InputObject $vsRequirements -Name 'Prefer64bit') { $vsRequirements.Prefer64bit } else { $false } if ($local:Prefer64bit -and (Test-Path(Join-Path $local:BinFolder "amd64"))) { $global:_MSBuildExe = Join-Path $local:BinFolder "amd64\msbuild.exe" } else { $global:_MSBuildExe = Join-Path $local:BinFolder "msbuild.exe" } return $global:_MSBuildExe } function InitializeVisualStudioEnvironmentVariables([string] $vsInstallDir, [string] $vsMajorVersion) { $env:VSINSTALLDIR = $vsInstallDir Set-Item "env:VS$($vsMajorVersion)0COMNTOOLS" (Join-Path $vsInstallDir "Common7\Tools\") $vsSdkInstallDir = Join-Path $vsInstallDir "VSSDK\" if (Test-Path $vsSdkInstallDir) { Set-Item "env:VSSDK$($vsMajorVersion)0Install" $vsSdkInstallDir $env:VSSDKInstall = $vsSdkInstallDir } } # # Locates Visual Studio instance that meets the minimal requirements specified by tools.vs object in global.json. # # The following properties of tools.vs are recognized: # "version": "{major}.{minor}" # Two part minimal VS version, e.g. "15.9", "16.0", etc. # "components": ["componentId1", "componentId2", ...] # Array of ids of workload components that must be available in the VS instance. # See e.g. https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-enterprise?view=vs-2017 # # Returns JSON describing the located VS instance (same format as returned by vswhere), # or $null if no instance meeting the requirements is found on the machine. # function LocateVisualStudio([object]$vsRequirements = $null){ if (-not (IsWindowsPlatform)) { throw "Cannot run vswhere on non-Windows platforms." } if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') { $vswhereVersion = $GlobalJson.tools.vswhere } else { $vswhereVersion = '3.1.7' } $vsWhereDir = Join-Path $ToolsDir "vswhere\$vswhereVersion" $vsWhereExe = Join-Path $vsWhereDir 'vswhere.exe' if (!(Test-Path $vsWhereExe)) { Create-Directory $vsWhereDir Write-Host "Downloading vswhere $vswhereVersion" $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit Retry({ Invoke-WebRequest "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/vswhere/$vswhereVersion/vswhere.exe" -UseBasicParsing -OutFile $vswhereExe }) } if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs' -ErrorAction SilentlyContinue) { $vsRequirements = $GlobalJson.tools.vs } else { $vsRequirements = $null } } $args = @('-latest', '-format', 'json', '-requires', 'Microsoft.Component.MSBuild', '-products', '*') if (!$excludePrereleaseVS) { $args += '-prerelease' } if ($vsRequirements -and (Get-Member -InputObject $vsRequirements -Name 'version' -ErrorAction SilentlyContinue)) { $args += '-version' $args += $vsRequirements.version } if ($vsRequirements -and (Get-Member -InputObject $vsRequirements -Name 'components' -ErrorAction SilentlyContinue)) { foreach ($component in $vsRequirements.components) { $args += '-requires' $args += $component } } $vsInfo =& $vsWhereExe $args | ConvertFrom-Json if ($lastExitCode -ne 0) { return $null } if ($null -eq $vsInfo -or $vsInfo.Count -eq 0) { throw "No instance of Visual Studio meeting the requirements specified was found. Requirements: $($args -join ' ')" return $null } # use first matching instance return $vsInfo[0] } function InitializeBuildTool() { if (Test-Path variable:global:_BuildTool) { # If the requested msbuild parameters do not match, clear the cached variables. if($global:_BuildTool.Contains('ExcludePrereleaseVS') -and $global:_BuildTool.ExcludePrereleaseVS -ne $excludePrereleaseVS) { Remove-Item variable:global:_BuildTool Remove-Item variable:global:_MSBuildExe } else { return $global:_BuildTool } } if (-not $msbuildEngine) { $msbuildEngine = GetDefaultMSBuildEngine } # Initialize dotnet cli if listed in 'tools' $dotnetRoot = $null if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { $dotnetRoot = InitializeDotNetCli -install:$restore } if ($msbuildEngine -eq 'dotnet') { if (!$dotnetRoot) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "/global.json must specify 'tools.dotnet'." ExitWithExitCode 1 } $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'net' } } elseif ($msbuildEngine -eq "vs") { try { $msbuildPath = InitializeVisualStudioMSBuild } catch { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 } $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "netframework"; ExcludePrereleaseVS = $excludePrereleaseVS } } else { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." ExitWithExitCode 1 } return $global:_BuildTool = $buildTool } function GetDefaultMSBuildEngine() { # Presence of tools.vs indicates the repo needs to build using VS msbuild on Windows. if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { return 'vs' } if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { return 'dotnet' } Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." ExitWithExitCode 1 } function GetNuGetPackageCachePath() { if ($env:NUGET_PACKAGES -eq $null) { # Use local cache on CI to ensure deterministic build. # Avoid using the http cache as workaround for https://github.com/NuGet/Home/issues/3116 # use global cache in dev builds to avoid cost of downloading packages. # For directory normalization, see also: https://github.com/NuGet/Home/issues/7968 if ($useGlobalNuGetCache) { $env:NUGET_PACKAGES = Join-Path $env:UserProfile '.nuget\packages\' } else { $env:NUGET_PACKAGES = Join-Path $RepoRoot '.packages\' } } return $env:NUGET_PACKAGES } # Returns a full path to an Arcade SDK task project file. function GetSdkTaskProject([string]$taskName) { $toolsetDir = Split-Path (InitializeToolset) -Parent $proj = Join-Path $toolsetDir "$taskName.proj" if (Test-Path $proj) { return $proj } # TODO: Remove this fallback once all supported versions use the new layout. $legacyProj = Join-Path $toolsetDir "SdkTasks\$taskName.proj" if (Test-Path $legacyProj) { return $legacyProj } throw "Unable to find $taskName.proj in toolset at: $toolsetDir" } function InitializeNativeTools() { if (-Not (Test-Path variable:DisableNativeToolsetInstalls) -And (Get-Member -InputObject $GlobalJson -Name "native-tools")) { $nativeArgs= @{} if ($ci) { $nativeArgs = @{ InstallDirectory = "$ToolsDir" } } if ($env:NativeToolsOnMachine) { Write-Host "Variable NativeToolsOnMachine detected, enabling native tool path promotion..." $nativeArgs += @{ PathPromotion = $true } } & "$PSScriptRoot/init-tools-native.ps1" @nativeArgs } } function Read-ArcadeSdkVersion() { return $GlobalJson.'msbuild-sdks'.'Microsoft.DotNet.Arcade.Sdk' } function InitializeToolset() { # For Unified Build/Source-build support, check whether the environment variable is # set. If it is, then use this as the toolset build project. if ($env:_InitializeToolset -ne $null) { return $global:_InitializeToolset = $env:_InitializeToolset } if (Test-Path variable:global:_InitializeToolset) { return $global:_InitializeToolset } $nugetCache = GetNuGetPackageCachePath $toolsetVersion = Read-ArcadeSdkVersion $toolsetToolsDir = Join-Path $ToolsetDir $toolsetVersion # Check if the toolset has already been extracted $toolsetBuildProj = $null $buildProjPath = Join-Path $toolsetToolsDir 'Build.proj' if (Test-Path $buildProjPath) { $toolsetBuildProj = $buildProjPath } if ($toolsetBuildProj -ne $null) { return $global:_InitializeToolset = $toolsetBuildProj } if (-not $restore) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Toolset version $toolsetVersion has not been restored." ExitWithExitCode 1 } $downloadArgs = @("package", "download", "Microsoft.DotNet.Arcade.Sdk@$toolsetVersion", "--verbosity", "minimal", "--prerelease", "--output", "$nugetCache") $nugetConfig = $env:NUGET_CONFIG if (-not $nugetConfig) { # Search for any variation of nuget.config in the RepoRoot $configFile = Get-ChildItem -Path $RepoRoot -File | Where-Object { $_.Name -ieq "nuget.config" } | Select-Object -First 1 if ($configFile) { $nugetConfig = $configFile.FullName } } if ($nugetConfig) { $downloadArgs += "--configfile" $downloadArgs += $nugetConfig } DotNet @downloadArgs $packageDir = Join-Path $nugetCache (Join-Path 'microsoft.dotnet.arcade.sdk' $toolsetVersion) $packageToolsetDir = Join-Path $packageDir 'toolset' $packageToolsDir = Join-Path $packageDir 'tools' # TODO: Remove the tools/ check once all supported versions have the toolset folder. if (!(Test-Path $packageToolsetDir) -and !(Test-Path $packageToolsDir)) { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Arcade SDK package does not contain a toolset or tools folder: $packageDir" ExitWithExitCode 3 } New-Item -ItemType Directory -Path $toolsetToolsDir -Force | Out-Null # Copy toolset if present at the package root (new layout), otherwise fall back to tools if (Test-Path $packageToolsetDir) { Copy-Item -Path "$packageToolsetDir\*" -Destination $toolsetToolsDir -Recurse -Force } else { # TODO: Remove this fallback once all supported versions have the toolset folder. Copy-Item -Path "$packageToolsDir\*" -Destination $toolsetToolsDir -Recurse -Force } if (Test-Path $buildProjPath) { $toolsetBuildProj = $buildProjPath } else { throw "Unable to find Build.proj in toolset at: $toolsetToolsDir" } return $global:_InitializeToolset = $toolsetBuildProj } function ExitWithExitCode([int] $exitCode) { if ($ci -and $prepareMachine) { Stop-Processes } exit $exitCode } # Check if $LASTEXITCODE is a nonzero exit code (NZEC). If so, print a Azure Pipeline error for # diagnostics, then exit the script with the $LASTEXITCODE. function Exit-IfNZEC([string] $category = "General") { Write-Host "Exit code $LASTEXITCODE" if ($LASTEXITCODE -ne 0) { $message = "Last command failed with exit code $LASTEXITCODE." Write-PipelineTelemetryError -Force -Category $category -Message $message ExitWithExitCode $LASTEXITCODE } } function Stop-Processes() { Write-Host 'Killing running build processes...' foreach ($processName in $processesToStopOnExit) { Get-Process -Name $processName -ErrorAction SilentlyContinue | Stop-Process } } # # Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. # The arguments are automatically quoted. # Terminates the script if the build fails. # function MSBuild() { if ($pipelinesLog) { $buildTool = InitializeBuildTool if ($ci -and $buildTool.Tool -eq 'dotnet') { $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20 $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20 Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20' Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20' } Enable-Nuget-EnhancedRetry $toolsetBuildProject = InitializeToolset $basePath = Split-Path -parent $toolsetBuildProject $selectedPath = Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.ArcadeLogging.dll') if (-not $selectedPath) { Write-PipelineTelemetryError -Category 'Build' -Message "Unable to find arcade sdk logger assembly: $selectedPath" ExitWithExitCode 1 } $args += "/logger:$selectedPath" } MSBuild-Core @args } # # Executes a dotnet command with arguments passed to the function. # Terminates the script if the command fails. # function DotNet() { $dotnetRoot = InitializeDotNetCli -install:$restore $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') $cmdArgs = "" foreach ($arg in $args) { if ($null -ne $arg -and $arg.Trim() -ne "") { if ($arg.EndsWith('\')) { $arg = $arg + "\" } $cmdArgs += " `"$arg`"" } } $env:ARCADE_BUILD_TOOL_COMMAND = "`"$dotnetPath`" $cmdArgs" $exitCode = Exec-Process $dotnetPath $cmdArgs if ($exitCode -ne 0) { Write-Host "dotnet command failed with exit code $exitCode. Check errors above." -ForegroundColor Red if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$fromVMR) { Write-PipelineSetResult -Result "Failed" -Message "dotnet command execution failed." ExitWithExitCode 0 } else { ExitWithExitCode $exitCode } } } # # Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. # The arguments are automatically quoted. # Terminates the script if the build fails. # function MSBuild-Core() { if ($ci) { if (!$binaryLog -and !$excludeCIBinarylog) { Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build, or explicitly opted-out from with the -excludeCIBinarylog switch.' ExitWithExitCode 1 } if ($nodeReuse) { Write-PipelineTelemetryError -Category 'Build' -Message 'Node reuse must be disabled in CI build.' ExitWithExitCode 1 } } Enable-Nuget-EnhancedRetry $buildTool = InitializeBuildTool $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" if ($ci -and $buildTool.Tool -eq 'dotnet') { $cmdArgs += ' /p:MSBuildEnableWorkloadResolver=false' } # Add -mt flag for MSBuild multithreaded mode if enabled via environment variable if ($env:MSBUILD_MT_ENABLED -eq "1") { $cmdArgs += ' -mt' } if ($warnAsError) { $cmdArgs += ' /warnaserror /p:TreatWarningsAsErrors=true' } else { $cmdArgs += ' /p:TreatWarningsAsErrors=false' } if ($warnAsError -and $warnNotAsError) { $cmdArgs += " /warnnotaserror:$warnNotAsError /p:AdditionalWarningsNotAsErrors=$warnNotAsError" } foreach ($arg in $args) { if ($null -ne $arg -and $arg.Trim() -ne "") { if ($arg.EndsWith('\')) { $arg = $arg + "\" } $cmdArgs += " `"$arg`"" } } # Be sure quote the path in case there are spaces in the dotnet installation location. $env:ARCADE_BUILD_TOOL_COMMAND = "`"$($buildTool.Path)`" $cmdArgs" $exitCode = Exec-Process $buildTool.Path $cmdArgs if ($exitCode -ne 0) { # We should not Write-PipelineTaskError here because that message shows up in the build summary # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. Write-Host "Build failed with exit code $exitCode. Check errors above." -ForegroundColor Red $buildLog = GetMSBuildBinaryLogCommandLineArgument $args if ($null -ne $buildLog) { Write-Host "See log: $buildLog" -ForegroundColor DarkGray } # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR build. if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$fromVMR) { Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error ExitWithExitCode 0 } else { ExitWithExitCode $exitCode } } } function GetMSBuildBinaryLogCommandLineArgument($arguments) { foreach ($argument in $arguments) { if ($argument -ne $null) { $arg = $argument.Trim() if ($arg.StartsWith('/bl:', "OrdinalIgnoreCase")) { return $arg.Substring('/bl:'.Length) } if ($arg.StartsWith('/binaryLogger:', 'OrdinalIgnoreCase')) { return $arg.Substring('/binaryLogger:'.Length) } } } return $null } function GetExecutableFileName($baseName) { if (IsWindowsPlatform) { return "$baseName.exe" } else { return $baseName } } function IsWindowsPlatform() { return [environment]::OSVersion.Platform -eq [PlatformID]::Win32NT } function Get-Darc($version) { $darcPath = "$TempDir\darc\$([guid]::NewGuid())" if ($version -ne $null) { & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath -darcVersion $version | Out-Host } else { & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath | Out-Host } return "$darcPath\darc.exe" } . $PSScriptRoot\pipeline-logging-functions.ps1 $RepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\') $EngRoot = Resolve-Path (Join-Path $PSScriptRoot '..') $ArtifactsDir = Join-Path $RepoRoot 'artifacts' $ToolsetDir = Join-Path $ArtifactsDir 'toolset' $ToolsDir = Join-Path $RepoRoot '.tools' $LogDir = Join-Path (Join-Path $ArtifactsDir 'log') $configuration $TempDir = Join-Path (Join-Path $ArtifactsDir 'tmp') $configuration $GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot 'global.json') | ConvertFrom-Json # true if global.json contains a "runtimes" section $globalJsonHasRuntimes = if ($GlobalJson.tools.PSObject.Properties.Name -Match 'runtimes') { $true } else { $false } Create-Directory $ToolsetDir Create-Directory $TempDir Create-Directory $LogDir # Direct MSBuild crash diagnostics (MSB4166 failure.txt files) to a known location # under artifacts/log so they are captured as build artifacts in CI. if (-not $env:MSBUILDDEBUGPATH) { $env:MSBUILDDEBUGPATH = Join-Path $LogDir 'MsbuildDebugLogs' } Write-PipelineSetVariable -Name 'Artifacts' -Value $ArtifactsDir Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir Write-PipelineSetVariable -Name 'TEMP' -Value $TempDir Write-PipelineSetVariable -Name 'TMP' -Value $TempDir # Import custom tools configuration, if present in the repo. # Note: Import in global scope so that the script set top-level variables without qualification. if (!$disableConfigureToolsetImport) { $configureToolsetScript = Join-Path $EngRoot 'configure-toolset.ps1' if (Test-Path $configureToolsetScript) { . $configureToolsetScript if ((Test-Path variable:failOnConfigureToolsetError) -And $failOnConfigureToolsetError) { if ((Test-Path variable:LastExitCode) -And ($LastExitCode -ne 0)) { Write-PipelineTelemetryError -Category 'Build' -Message 'configure-toolset.ps1 returned a non-zero exit code' ExitWithExitCode $LastExitCode } } } } # # If $ci flag is set, turn on (and log that we did) special environment variables for improved Nuget client retry logic. # function Enable-Nuget-EnhancedRetry() { if ($ci) { Write-Host "Setting NUGET enhanced retry environment variables" $env:NUGET_ENABLE_ENHANCED_HTTP_RETRY = 'true' $env:NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT = 6 $env:NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS = 1000 $env:NUGET_RETRY_HTTP_429 = 'true' Write-PipelineSetVariable -Name 'NUGET_ENABLE_ENHANCED_HTTP_RETRY' -Value 'true' Write-PipelineSetVariable -Name 'NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT' -Value '6' Write-PipelineSetVariable -Name 'NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS' -Value '1000' Write-PipelineSetVariable -Name 'NUGET_RETRY_HTTP_429' -Value 'true' } } ================================================ FILE: eng/common/tools.sh ================================================ #!/usr/bin/env bash # Initialize variables if they aren't already defined. # CI mode - set to true on CI server for PR validation build or official build. ci=${ci:-false} # Build mode source_build=${source_build:-false} # Set to true to use the pipelines logger which will enable Azure logging output. # https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md # This flag is meant as a temporary opt-opt for the feature while validate it across # our consumers. It will be deleted in the future. if [[ "$ci" == true ]]; then pipelines_log=${pipelines_log:-true} else pipelines_log=${pipelines_log:-false} fi # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. configuration=${configuration:-'Debug'} # Set to true to opt out of outputting binary log while running in CI exclude_ci_binary_log=${exclude_ci_binary_log:-false} if [[ "$ci" == true && "$exclude_ci_binary_log" == false ]]; then binary_log_default=true else binary_log_default=false fi # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. binary_log=${binary_log:-$binary_log_default} # Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes). prepare_machine=${prepare_machine:-false} # True to restore toolsets and dependencies. restore=${restore:-true} # Adjusts msbuild verbosity level. verbosity=${verbosity:-'minimal'} # Set to true to reuse msbuild nodes. Recommended to not reuse on CI. if [[ "$ci" == true ]]; then node_reuse=${node_reuse:-false} else node_reuse=${node_reuse:-true} fi # Configures warning treatment in msbuild. warn_as_error=${warn_as_error:-true} # Specifies semi-colon delimited list of warning codes that should not be treated as errors. warn_not_as_error=${warn_not_as_error:-''} # True to attempt using .NET Core already that meets requirements specified in global.json # installed on the machine instead of downloading one. use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} # Enable repos to use a particular version of the on-line dotnet-install scripts. # default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'} # True to use global NuGet cache instead of restoring packages to repository-local directory. # Keep in sync with NuGetPackageroot in Arcade SDK's RepositoryLayout.props. if [[ "$ci" == true || "$source_build" == true ]]; then use_global_nuget_cache=${use_global_nuget_cache:-false} else use_global_nuget_cache=${use_global_nuget_cache:-true} fi # Used when restoring .NET SDK from alternative feeds runtime_source_feed=${runtime_source_feed:-''} runtime_source_feed_key=${runtime_source_feed_key:-''} # True when the build is running within the VMR. from_vmr=${from_vmr:-false} # Resolve any symlinks in the given path. function ResolvePath { local path=$1 while [[ -h $path ]]; do local dir="$( cd -P "$( dirname "$path" )" && pwd )" path="$(readlink "$path")" # if $path was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $path != /* ]] && path="$dir/$path" done # return value _ResolvePath="$path" } # ReadVersionFromJson [json key] function ReadGlobalVersion { local key=$1 if command -v jq &> /dev/null; then _ReadGlobalVersion="$(jq -r ".[] | select(has(\"$key\")) | .\"$key\"" "$global_json_file")" elif [[ "$(cat "$global_json_file")" =~ \"$key\"[[:space:]\:]*\"([^\"]+) ]]; then _ReadGlobalVersion=${BASH_REMATCH[1]} fi if [[ -z "$_ReadGlobalVersion" ]]; then Write-PipelineTelemetryError -category 'Build' "Error: Cannot find \"$key\" in $global_json_file" ExitWithExitCode 1 fi } function InitializeDotNetCli { if [[ -n "${_InitializeDotNetCli:-}" ]]; then return fi local install=$1 # Disable first run since we want to control all package sources export DOTNET_NOLOGO=1 # Disable telemetry on CI if [[ $ci == true ]]; then export DOTNET_CLI_TELEMETRY_OPTOUT=1 fi # Keep repo builds isolated from machine-installed SDK state and workload advertising. # This avoids preview SDK builds picking up mismatched workloads on CI images. export DOTNET_MULTILEVEL_LOOKUP=0 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 export DOTNET_CLI_WORKLOAD_UPDATE_NOTIFY_DISABLE=1 # LTTNG is the logging infrastructure used by Core CLR. Need this variable set # so it doesn't output warnings to the console. export LTTNG_HOME="$HOME" # Find the first path on $PATH that contains the dotnet.exe if [[ "$use_installed_dotnet_cli" == true && $global_json_has_runtimes == false && -z "${DOTNET_INSTALL_DIR:-}" ]]; then local dotnet_path=`command -v dotnet` if [[ -n "$dotnet_path" ]]; then ResolvePath "$dotnet_path" export DOTNET_INSTALL_DIR=`dirname "$_ResolvePath"` fi fi ReadGlobalVersion "dotnet" local dotnet_sdk_version=$_ReadGlobalVersion local dotnet_root="" # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. if [[ $global_json_has_runtimes == false && -n "${DOTNET_INSTALL_DIR:-}" && -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then dotnet_root="$DOTNET_INSTALL_DIR" else if [[ -n "${DOTNET_GLOBAL_INSTALL_DIR:-}" ]]; then dotnet_root="$DOTNET_GLOBAL_INSTALL_DIR" else dotnet_root="${repo_root}.dotnet" fi export DOTNET_INSTALL_DIR="$dotnet_root" if [[ ! -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then if [[ "$install" == true ]]; then InstallDotNetSdk "$dotnet_root" "$dotnet_sdk_version" else Write-PipelineTelemetryError -category 'InitializeToolset' "Unable to find dotnet with SDK version '$dotnet_sdk_version'" ExitWithExitCode 1 fi fi fi # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom # build steps from using anything other than what we've downloaded. Write-PipelinePrependPath -path "$dotnet_root" Write-PipelineSetVariable -name "DOTNET_NOLOGO" -value "1" Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0" Write-PipelineSetVariable -name "DOTNET_SKIP_FIRST_TIME_EXPERIENCE" -value "1" Write-PipelineSetVariable -name "DOTNET_CLI_WORKLOAD_UPDATE_NOTIFY_DISABLE" -value "1" # return value _InitializeDotNetCli="$dotnet_root" } function InstallDotNetSdk { local root=$1 local version=$2 local architecture="unset" if [[ $# -ge 3 ]]; then architecture=$3 fi InstallDotNet "$root" "$version" $architecture 'sdk' 'true' $runtime_source_feed $runtime_source_feed_key } function InstallDotNet { local root=$1 local version=$2 local runtime=$4 # For performance this check is duplicated in src/Microsoft.DotNet.Arcade.Sdk/src/InstallDotNetCore.cs # if you are making changes here, consider if you need to make changes there as well. local dotnetVersionLabel="'$runtime v$version'" if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then runtimePath="$root" runtimePath="$runtimePath/shared" case "$runtime" in dotnet) runtimePath="$runtimePath/Microsoft.NETCore.App" ;; aspnetcore) runtimePath="$runtimePath/Microsoft.AspNetCore.App" ;; windowsdesktop) runtimePath="$runtimePath/Microsoft.WindowsDesktop.App" ;; *) ;; esac runtimePath="$runtimePath/$version" dotnetVersionLabel="runtime toolset '$runtime/$architecture v$version'" if [ -d "$runtimePath" ]; then echo " Runtime toolset '$runtime/$architecture v$version' already installed." local installSuccess=1 return fi fi GetDotNetInstallScript "$root" local install_script=$_GetDotNetInstallScript local installParameters=(--version $version --install-dir "$root") if [[ -n "${3:-}" ]] && [ "$3" != 'unset' ]; then installParameters+=(--architecture $3) fi if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then installParameters+=(--runtime $4) fi if [[ "$#" -ge "5" ]] && [[ "$5" != 'false' ]]; then installParameters+=(--skip-non-versioned-files) fi local variations=() # list of variable names with parameter arrays in them local public_location=("${installParameters[@]}") variations+=(public_location) local dotnetbuilds=("${installParameters[@]}" --azure-feed "https://ci.dot.net/public") variations+=(dotnetbuilds) if [[ -n "${6:-}" ]]; then variations+=(private_feed) local private_feed=("${installParameters[@]}" --azure-feed $6) if [[ -n "${7:-}" ]]; then # The 'base64' binary on alpine uses '-d' and doesn't support '--decode' # '-d'. To work around this, do a simple detection and switch the parameter # accordingly. decodeArg="--decode" if base64 --help 2>&1 | grep -q "BusyBox"; then decodeArg="-d" fi decodedFeedKey=`echo $7 | base64 $decodeArg` private_feed+=(--feed-credential $decodedFeedKey) fi fi local installSuccess=0 for variationName in "${variations[@]}"; do local name="$variationName[@]" local variation=("${!name}") echo " Attempting to install $dotnetVersionLabel from $variationName." bash "$install_script" "${variation[@]}" && installSuccess=1 if [[ "$installSuccess" -eq 1 ]]; then break fi echo " Failed to install $dotnetVersionLabel from $variationName." done if [[ "$installSuccess" -eq 0 ]]; then Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install $dotnetVersionLabel from any of the specified locations." ExitWithExitCode 1 fi } function with_retries { local maxRetries=5 local retries=1 echo "Trying to run '$@' for maximum of $maxRetries attempts." while [[ $((retries++)) -le $maxRetries ]]; do "$@" if [[ $? == 0 ]]; then echo "Ran '$@' successfully." return 0 fi timeout=$((3**$retries-1)) echo "Failed to execute '$@'. Waiting $timeout seconds before next attempt ($retries out of $maxRetries)." 1>&2 sleep $timeout done echo "Failed to execute '$@' for $maxRetries times." 1>&2 return 1 } function GetDotNetInstallScript { local root=$1 local install_script="$root/dotnet-install.sh" local install_script_url="https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" local timestamp_file="$root/.dotnet-install.timestamp" local should_download=false if [[ ! -a "$install_script" ]]; then should_download=true elif [[ -f "$timestamp_file" ]]; then # Check if the script is older than 30 days using timestamp file local download_time=$(cat "$timestamp_file" 2>/dev/null || echo "0") local current_time=$(date +%s) local age_seconds=$((current_time - download_time)) # 30 days = 30 * 24 * 60 * 60 = 2592000 seconds if [[ $age_seconds -gt 2592000 ]]; then echo "Existing install script is too old, re-downloading..." should_download=true fi else # No timestamp file exists, assume script is old and re-download echo "No timestamp found for existing install script, re-downloading..." should_download=true fi if [[ "$should_download" == true ]]; then mkdir -p "$root" echo "Downloading '$install_script_url'" # Use curl if available, otherwise use wget if command -v curl > /dev/null; then # first, try directly, if this fails we will retry with verbose logging curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" || { if command -v openssl &> /dev/null; then echo "Curl failed; dumping some information about dotnet.microsoft.com for later investigation" echo | openssl s_client -showcerts -servername dotnet.microsoft.com -connect dotnet.microsoft.com:443 || true fi echo "Will now retry the same URL with verbose logging." with_retries curl "$install_script_url" -sSL --verbose --retry 10 --create-dirs -o "$install_script" || { local exit_code=$? Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." ExitWithExitCode $exit_code } } else with_retries wget -v -O "$install_script" "$install_script_url" || { local exit_code=$? Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." ExitWithExitCode $exit_code } fi # Create timestamp file to track download time in seconds from epoch date +%s > "$timestamp_file" fi # return value _GetDotNetInstallScript="$install_script" } function InitializeBuildTool { if [[ -n "${_InitializeBuildTool:-}" ]]; then return fi InitializeDotNetCli $restore # return values _InitializeBuildTool="$_InitializeDotNetCli/dotnet" _InitializeBuildToolCommand="msbuild" } function GetNuGetPackageCachePath { if [[ -z ${NUGET_PACKAGES:-} ]]; then if [[ "$use_global_nuget_cache" == true ]]; then export NUGET_PACKAGES="$HOME/.nuget/packages/" else export NUGET_PACKAGES="$repo_root/.packages/" fi fi # return value _GetNuGetPackageCachePath=$NUGET_PACKAGES } function InitializeNativeTools() { if [[ -n "${DisableNativeToolsetInstalls:-}" ]]; then return fi if grep -Fq "native-tools" $global_json_file then local nativeArgs="" if [[ "$ci" == true ]]; then nativeArgs="--installDirectory $tools_dir" fi "$_script_dir/init-tools-native.sh" $nativeArgs fi } function InitializeToolset { if [[ -n "${_InitializeToolset:-}" ]]; then return fi GetNuGetPackageCachePath ReadGlobalVersion "Microsoft.DotNet.Arcade.Sdk" local toolset_version=$_ReadGlobalVersion local toolset_tools_dir="$toolset_dir/$toolset_version" # Check if the toolset has already been extracted local toolset_build_proj="" if [[ -a "$toolset_tools_dir/Build.proj" ]]; then toolset_build_proj="$toolset_tools_dir/Build.proj" fi if [[ -n "$toolset_build_proj" ]]; then # return value _InitializeToolset="$toolset_build_proj" return fi if [[ "$restore" != true ]]; then Write-PipelineTelemetryError -category 'InitializeToolset' "Toolset version $toolset_version has not been restored." ExitWithExitCode 2 fi local download_args=("package" "download" "Microsoft.DotNet.Arcade.Sdk@$toolset_version" "--verbosity" "minimal" "--prerelease" "--output" "$_GetNuGetPackageCachePath") local nuget_config="${NUGET_CONFIG:-}" if [[ -z "$nuget_config" ]]; then # Search for any variation of nuget.config in the RepoRoot local found_config found_config=$(find "$repo_root" -maxdepth 1 -type f -iname "nuget.config" -print -quit) if [[ -n "$found_config" ]]; then nuget_config="$found_config" fi fi if [[ -n "$nuget_config" ]]; then download_args+=("--configfile" "$nuget_config") fi DotNet "${download_args[@]}" local package_dir="$_GetNuGetPackageCachePath/microsoft.dotnet.arcade.sdk/$toolset_version" # TODO: Remove the tools/ check once all supported versions have the toolset folder. if [[ ! -d "$package_dir/toolset" && ! -d "$package_dir/tools" ]]; then Write-PipelineTelemetryError -category 'InitializeToolset' "Arcade SDK package does not contain a toolset or tools folder: $package_dir" ExitWithExitCode 3 fi mkdir -p "$toolset_tools_dir" # Copy toolset if present at the package root (new layout), otherwise fall back to tools if [[ -d "$package_dir/toolset" ]]; then cp -r "$package_dir/toolset/." "$toolset_tools_dir" else # TODO: Remove this fallback once all supported versions have the toolset folder. cp -r "$package_dir/tools/." "$toolset_tools_dir" fi if [[ -a "$toolset_tools_dir/Build.proj" ]]; then toolset_build_proj="$toolset_tools_dir/Build.proj" else Write-PipelineTelemetryError -category 'Build' "Unable to find Build.proj in toolset at: $toolset_tools_dir" ExitWithExitCode 3 fi # return value _InitializeToolset="$toolset_build_proj" } function ExitWithExitCode { if [[ "$ci" == true && "$prepare_machine" == true ]]; then StopProcesses fi exit $1 } function StopProcesses { echo "Killing running build processes..." pkill -9 "dotnet" || true pkill -9 "vbcscompiler" || true return 0 } function DotNet { InitializeDotNetCli $restore local dotnet_path="$_InitializeDotNetCli/dotnet" export ARCADE_BUILD_TOOL_COMMAND="$dotnet_path $@" "$dotnet_path" "$@" || { local exit_code=$? echo "dotnet command failed with exit code $exit_code. Check errors above." if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$from_vmr" != true ]]; then Write-PipelineSetResult -result "Failed" -message "dotnet command execution failed." ExitWithExitCode 0 else ExitWithExitCode $exit_code fi } } function MSBuild { local args=( "$@" ) if [[ "$pipelines_log" == true ]]; then InitializeBuildTool InitializeToolset if [[ "$ci" == true ]]; then export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20" Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20" fi local toolset_dir="${_InitializeToolset%/*}" local selectedPath="$toolset_dir/net/Microsoft.DotNet.ArcadeLogging.dll" if [[ -z "$selectedPath" ]]; then Write-PipelineTelemetryError -category 'Build' "Unable to find arcade sdk logger assembly: $selectedPath" ExitWithExitCode 1 fi args+=( "-logger:$selectedPath" ) fi MSBuild-Core "${args[@]}" } function MSBuild-Core { if [[ "$ci" == true ]]; then if [[ "$binary_log" != true && "$exclude_ci_binary_log" != true ]]; then Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the -noBinaryLog switch." ExitWithExitCode 1 fi if [[ "$node_reuse" == true ]]; then Write-PipelineTelemetryError -category 'Build' "Node reuse must be disabled in CI build." ExitWithExitCode 1 fi fi InitializeBuildTool local warnaserror_switch="" if [[ $warn_as_error == true ]]; then warnaserror_switch="/warnaserror" fi function RunBuildTool { export ARCADE_BUILD_TOOL_COMMAND="$_InitializeBuildTool $@" "$_InitializeBuildTool" "$@" || { local exit_code=$? # We should not Write-PipelineTaskError here because that message shows up in the build summary # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. echo "Build failed with exit code $exit_code. Check errors above." # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR build. if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$from_vmr" != true ]]; then Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error ExitWithExitCode 0 else ExitWithExitCode $exit_code fi } } # Add -mt flag for MSBuild multithreaded mode if enabled via environment variable local mt_switch="" if [[ "${MSBUILD_MT_ENABLED:-}" == "1" ]]; then mt_switch="-mt" fi local warnnotaserror_switch="" if [[ -n "$warn_not_as_error" && "$warn_as_error" == true ]]; then warnnotaserror_switch="/warnnotaserror:$warn_not_as_error /p:AdditionalWarningsNotAsErrors=$warn_not_as_error" fi local workload_resolver_switch="" if [[ "$ci" == true && -n "${_InitializeBuildToolCommand:-}" ]]; then workload_resolver_switch="/p:MSBuildEnableWorkloadResolver=false" fi RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch $mt_switch $warnnotaserror_switch $workload_resolver_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" } function GetDarc { darc_path="$temp_dir/darc" version="$1" if [[ -n "$version" ]]; then version="--darcversion $version" fi "$eng_root/common/darc-init.sh" --toolpath "$darc_path" $version darc_tool="$darc_path/darc" } # Returns a full path to an Arcade SDK task project file. function GetSdkTaskProject { local taskName=$1 local toolsetDir toolsetDir="$(dirname "$_InitializeToolset")" local proj="$toolsetDir/$taskName.proj" if [[ -a "$proj" ]]; then echo "$proj" return fi # TODO: Remove this fallback once all supported versions use the new layout. local legacyProj="$toolsetDir/SdkTasks/$taskName.proj" if [[ -a "$legacyProj" ]]; then echo "$legacyProj" return fi Write-PipelineTelemetryError -category 'Build' "Unable to find $taskName.proj in toolset at: $toolsetDir" ExitWithExitCode 3 } ResolvePath "${BASH_SOURCE[0]}" _script_dir=`dirname "$_ResolvePath"` . "$_script_dir/pipeline-logging-functions.sh" eng_root=`cd -P "$_script_dir/.." && pwd` repo_root=`cd -P "$_script_dir/../.." && pwd` repo_root="${repo_root}/" artifacts_dir="${repo_root}artifacts" toolset_dir="$artifacts_dir/toolset" tools_dir="${repo_root}.tools" log_dir="$artifacts_dir/log/$configuration" temp_dir="$artifacts_dir/tmp/$configuration" global_json_file="${repo_root}global.json" # determine if global.json contains a "runtimes" entry global_json_has_runtimes=false if command -v jq &> /dev/null; then if jq -e '.tools | has("runtimes")' "$global_json_file" &> /dev/null; then global_json_has_runtimes=true fi elif [[ "$(cat "$global_json_file")" =~ \"runtimes\"[[:space:]\:]*\{ ]]; then global_json_has_runtimes=true fi # HOME may not be defined in some scenarios, but it is required by NuGet if [[ -z $HOME ]]; then export HOME="${repo_root}artifacts/.home/" mkdir -p "$HOME" fi mkdir -p "$toolset_dir" mkdir -p "$temp_dir" mkdir -p "$log_dir" # Direct MSBuild crash diagnostics (MSB4166 failure.txt files) to a known location # under artifacts/log so they are captured as build artifacts in CI. if [[ -z "${MSBUILDDEBUGPATH:-}" ]]; then export MSBUILDDEBUGPATH="$log_dir/MsbuildDebugLogs" fi Write-PipelineSetVariable -name "Artifacts" -value "$artifacts_dir" Write-PipelineSetVariable -name "Artifacts.Toolset" -value "$toolset_dir" Write-PipelineSetVariable -name "Artifacts.Log" -value "$log_dir" Write-PipelineSetVariable -name "Temp" -value "$temp_dir" Write-PipelineSetVariable -name "TMP" -value "$temp_dir" # Import custom tools configuration, if present in the repo. if [ -z "${disable_configure_toolset_import:-}" ]; then configure_toolset_script="$eng_root/configure-toolset.sh" if [[ -a "$configure_toolset_script" ]]; then . "$configure_toolset_script" fi fi # TODO: https://github.com/dotnet/arcade/issues/1468 # Temporary workaround to avoid breaking change. # Remove once repos are updated. if [[ -n "${useInstalledDotNetCli:-}" ]]; then use_installed_dotnet_cli="$useInstalledDotNetCli" fi ================================================ FILE: eng/common/vmr-sync.ps1 ================================================ <# .SYNOPSIS This script is used for synchronizing the current repository into a local VMR. It pulls the current repository's code into the specified VMR directory for local testing or Source-Build validation. .DESCRIPTION The tooling used for synchronization will clone the VMR repository into a temporary folder if it does not already exist. These clones can be reused in future synchronizations, so it is recommended to dedicate a folder for this to speed up re-runs. .EXAMPLE Synchronize current repository into a local VMR: ./vmr-sync.ps1 -vmrDir "$HOME/repos/dotnet" -tmpDir "$HOME/repos/tmp" .PARAMETER tmpDir Required. Path to the temporary folder where repositories will be cloned .PARAMETER vmrBranch Optional. Branch of the 'dotnet/dotnet' repo to synchronize. The VMR will be checked out to this branch .PARAMETER azdevPat Optional. Azure DevOps PAT to use for cloning private repositories. .PARAMETER vmrDir Optional. Path to the dotnet/dotnet repository. When null, gets cloned to the temporary folder .PARAMETER debugOutput Optional. Enables debug logging in the darc vmr command. .PARAMETER ci Optional. Denotes that the script is running in a CI environment. #> param ( [Parameter(Mandatory=$true, HelpMessage="Path to the temporary folder where repositories will be cloned")] [string][Alias('t', 'tmp')]$tmpDir, [string][Alias('b', 'branch')]$vmrBranch, [string]$remote, [string]$azdevPat, [string][Alias('v', 'vmr')]$vmrDir, [switch]$ci, [switch]$debugOutput ) function Fail { Write-Host "> $($args[0])" -ForegroundColor 'Red' } function Highlight { Write-Host "> $($args[0])" -ForegroundColor 'Cyan' } $verbosity = 'verbose' if ($debugOutput) { $verbosity = 'debug' } # Validation if (-not $tmpDir) { Fail "Missing -tmpDir argument. Please specify the path to the temporary folder where the repositories will be cloned" exit 1 } # Sanitize the input if (-not $vmrDir) { $vmrDir = Join-Path $tmpDir 'dotnet' } if (-not (Test-Path -Path $tmpDir -PathType Container)) { New-Item -ItemType Directory -Path $tmpDir | Out-Null } # Prepare the VMR if (-not (Test-Path -Path $vmrDir -PathType Container)) { Highlight "Cloning 'dotnet/dotnet' into $vmrDir.." git clone https://github.com/dotnet/dotnet $vmrDir if ($vmrBranch) { git -C $vmrDir switch -c $vmrBranch } } else { if ((git -C $vmrDir diff --quiet) -eq $false) { Fail "There are changes in the working tree of $vmrDir. Please commit or stash your changes" exit 1 } if ($vmrBranch) { Highlight "Preparing $vmrDir" git -C $vmrDir checkout $vmrBranch git -C $vmrDir pull } } Set-StrictMode -Version Latest # Prepare darc Highlight 'Installing .NET, preparing the tooling..' . .\eng\common\tools.ps1 $dotnetRoot = InitializeDotNetCli -install:$true $env:DOTNET_ROOT = $dotnetRoot $darc = Get-Darc Highlight "Starting the synchronization of VMR.." # Synchronize the VMR $versionDetailsPath = Resolve-Path (Join-Path $PSScriptRoot '..\Version.Details.xml') | Select-Object -ExpandProperty Path [xml]$versionDetails = Get-Content -Path $versionDetailsPath $repoName = $versionDetails.SelectSingleNode('//Source').Mapping if (-not $repoName) { Fail "Failed to resolve repo mapping from $versionDetailsPath" exit 1 } $darcArgs = ( "vmr", "forwardflow", "--tmp", $tmpDir, "--$verbosity", $vmrDir ) if ($ci) { $darcArgs += ("--ci") } if ($azdevPat) { $darcArgs += ("--azdev-pat", $azdevPat) } & "$darc" $darcArgs if ($LASTEXITCODE -eq 0) { Highlight "Synchronization succeeded" } else { Highlight "Failed to flow code into the local VMR. Falling back to resetting the VMR to match repo contents..." git -C $vmrDir reset --hard $resetArgs = ( "vmr", "reset", "${repoName}:HEAD", "--vmr", $vmrDir, "--tmp", $tmpDir, "--additional-remotes", "${repoName}:${repoRoot}" ) & "$darc" $resetArgs if ($LASTEXITCODE -eq 0) { Highlight "Successfully reset the VMR using 'darc vmr reset'" } else { Fail "Synchronization of repo to VMR failed!" Fail "'$vmrDir' is left in its last state (re-run of this script will reset it)." Fail "Please inspect the logs which contain path to the failing patch file (use -debugOutput to get all the details)." Fail "Once you make changes to the conflicting VMR patch, commit it locally and re-run this script." exit 1 } } ================================================ FILE: eng/common/vmr-sync.sh ================================================ #!/bin/bash ### This script is used for synchronizing the current repository into a local VMR. ### It pulls the current repository's code into the specified VMR directory for local testing or ### Source-Build validation. ### ### The tooling used for synchronization will clone the VMR repository into a temporary folder if ### it does not already exist. These clones can be reused in future synchronizations, so it is ### recommended to dedicate a folder for this to speed up re-runs. ### ### USAGE: ### Synchronize current repository into a local VMR: ### ./vmr-sync.sh --tmp "$HOME/repos/tmp" "$HOME/repos/dotnet" ### ### Options: ### -t, --tmp, --tmp-dir PATH ### Required. Path to the temporary folder where repositories will be cloned ### ### -b, --branch, --vmr-branch BRANCH_NAME ### Optional. Branch of the 'dotnet/dotnet' repo to synchronize. The VMR will be checked out to this branch ### ### --debug ### Optional. Turns on the most verbose logging for the VMR tooling ### ### --remote name:URI ### Optional. Additional remote to use during the synchronization ### This can be used to synchronize to a commit from a fork of the repository ### Example: 'runtime:https://github.com/yourfork/runtime' ### ### --azdev-pat ### Optional. Azure DevOps PAT to use for cloning private repositories. ### ### -v, --vmr, --vmr-dir PATH ### Optional. Path to the dotnet/dotnet repository. When null, gets cloned to the temporary folder source="${BASH_SOURCE[0]}" # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" function print_help () { sed -n '/^### /,/^$/p' "$source" | cut -b 5- } COLOR_RED=$(tput setaf 1 2>/dev/null || true) COLOR_CYAN=$(tput setaf 6 2>/dev/null || true) COLOR_CLEAR=$(tput sgr0 2>/dev/null || true) COLOR_RESET=uniquesearchablestring FAILURE_PREFIX='> ' function fail () { echo "${COLOR_RED}$FAILURE_PREFIX${1//${COLOR_RESET}/${COLOR_RED}}${COLOR_CLEAR}" >&2 } function highlight () { echo "${COLOR_CYAN}$FAILURE_PREFIX${1//${COLOR_RESET}/${COLOR_CYAN}}${COLOR_CLEAR}" } tmp_dir='' vmr_dir='' vmr_branch='' additional_remotes='' verbosity=verbose azdev_pat='' ci=false while [[ $# -gt 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in -t|--tmp|--tmp-dir) tmp_dir=$2 shift ;; -v|--vmr|--vmr-dir) vmr_dir=$2 shift ;; -b|--branch|--vmr-branch) vmr_branch=$2 shift ;; --remote) additional_remotes="$additional_remotes $2" shift ;; --azdev-pat) azdev_pat=$2 shift ;; --ci) ci=true ;; -d|--debug) verbosity=debug ;; -h|--help) print_help exit 0 ;; *) fail "Invalid argument: $1" print_help exit 1 ;; esac shift done # Validation if [[ -z "$tmp_dir" ]]; then fail "Missing --tmp-dir argument. Please specify the path to the temporary folder where the repositories will be cloned" exit 1 fi # Sanitize the input if [[ -z "$vmr_dir" ]]; then vmr_dir="$tmp_dir/dotnet" fi if [[ ! -d "$tmp_dir" ]]; then mkdir -p "$tmp_dir" fi if [[ "$verbosity" == "debug" ]]; then set -x fi # Prepare the VMR if [[ ! -d "$vmr_dir" ]]; then highlight "Cloning 'dotnet/dotnet' into $vmr_dir.." git clone https://github.com/dotnet/dotnet "$vmr_dir" if [[ -n "$vmr_branch" ]]; then git -C "$vmr_dir" switch -c "$vmr_branch" fi else if ! git -C "$vmr_dir" diff --quiet; then fail "There are changes in the working tree of $vmr_dir. Please commit or stash your changes" exit 1 fi if [[ -n "$vmr_branch" ]]; then highlight "Preparing $vmr_dir" git -C "$vmr_dir" checkout "$vmr_branch" git -C "$vmr_dir" pull fi fi set -e # Prepare darc highlight 'Installing .NET, preparing the tooling..' source "./eng/common/tools.sh" InitializeDotNetCli true GetDarc dotnetDir=$( cd ./.dotnet/; pwd -P ) dotnet=$dotnetDir/dotnet highlight "Starting the synchronization of VMR.." set +e if [[ -n "$additional_remotes" ]]; then additional_remotes="--additional-remotes $additional_remotes" fi if [[ -n "$azdev_pat" ]]; then azdev_pat="--azdev-pat $azdev_pat" fi ci_arg='' if [[ "$ci" == "true" ]]; then ci_arg="--ci" fi # Synchronize the VMR version_details_path=$(cd "$scriptroot/.."; pwd -P)/Version.Details.xml repo_name=$(grep -m 1 ' /// Exit codes we monitor from ADB commands /// public enum AdbExitCodes { SUCCESS = 0, INSTRUMENTATION_SUCCESS = -1, INSTRUMENTATION_TIMEOUT = -2, COMMAND_NOT_FOUND = 127, ADB_BROKEN_PIPE = 224, ADB_UNINSTALL_APP_NOT_ON_DEVICE = 255, ADB_UNINSTALL_APP_NOT_ON_EMULATOR = 1, } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/AdbFailureException.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.Android; public class AdbFailureException : Exception { public AdbFailureException(string message) : base(message) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/AdbRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using Microsoft.DotNet.XHarness.Android.Execution; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.Android; public class AdbRunner { private enum AdbProperty { Architecture, ApiVersion, SupportedArchitectures, InstalledApps, BootCompletion, } #region Constructor and state variables private static readonly Dictionary s_commandList = new() { { AdbProperty.SupportedArchitectures, new[] { "shell", "getprop", "ro.product.cpu.abilist" } }, { AdbProperty.ApiVersion, new[] { "shell", "getprop", "ro.build.version.sdk" } }, { AdbProperty.Architecture, new[] { "shell", "getprop", "ro.product.cpu.abi" } }, { AdbProperty.InstalledApps, new[] { "shell", "pm", "list", "packages", "-3" } }, { AdbProperty.BootCompletion, new[] { "shell", "getprop", "sys.boot_completed" } }, }; private const string AdbEnvironmentVariableName = "ADB_EXE_PATH"; private const string AdbDeviceFullInstallFailureMessage = "INSTALL_FAILED_INSUFFICIENT_STORAGE"; private const string AdbInstallBrokenPipeError = "Failure calling service package: Broken pipe"; private const string AdbInstallException = "Exception occurred while executing 'install':"; public const string GlobalReadWriteDirectory = "/data/local/tmp"; private readonly string _absoluteAdbExePath; private readonly ILogger _log; private readonly IAdbProcessManager _processManager; private AndroidDevice? _activeDevice = null; /// /// Returns the currently active device, if one has been selected. /// public AndroidDevice? GetActiveDevice() => _activeDevice; public AdbRunner(ILogger log, string adbExePath = "") : this(log, new AdbProcessManager(log), adbExePath) { } public AdbRunner(ILogger log, IAdbProcessManager processManager, string adbExePath = "") { _log = log ?? throw new ArgumentNullException(nameof(log)); // If we don't get passed one in, use the real implementation _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); // We need to find ADB.exe somewhere string? environmentPath = Environment.GetEnvironmentVariable(AdbEnvironmentVariableName); if (!string.IsNullOrEmpty(environmentPath)) { _log.LogDebug($"Using {AdbEnvironmentVariableName} environment variable ({environmentPath}) for ADB path"); adbExePath = environmentPath; } if (string.IsNullOrEmpty(adbExePath)) { adbExePath = GetCliAdbExePath(); } _absoluteAdbExePath = Path.GetFullPath(Environment.ExpandEnvironmentVariables(adbExePath)); if (!File.Exists(_absoluteAdbExePath)) { _log.LogError($"Unable to find adb.exe"); throw new FileNotFoundException($"Could not find adb.exe. Either set it in the environment via {AdbEnvironmentVariableName} or call with valid path (provided: '{adbExePath}')", adbExePath); } if (!_absoluteAdbExePath.Equals(adbExePath)) { _log.LogDebug($"ADBRunner using ADB.exe supplied from {adbExePath}"); _log.LogDebug($"Full resolved path:'{_absoluteAdbExePath}'"); } } private static string GetCliAdbExePath() { var currentAssemblyDirectory = Path.GetDirectoryName(typeof(AdbRunner).Assembly.Location); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return Path.Join(currentAssemblyDirectory, @"..\..\..\runtimes\any\native\adb\windows\adb.exe"); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return Path.Join(currentAssemblyDirectory, @"../../../runtimes/any/native/adb/linux/adb"); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return Path.Join(currentAssemblyDirectory, @"../../../runtimes/any/native/adb/macos/adb"); } throw new NotSupportedException("Cannot determine OS platform being used, thus we can not select an ADB executable"); } #endregion #region Functions public TimeSpan TimeToWaitForBootCompletion { get; set; } = TimeSpan.FromMinutes(5); public string GetAdbVersion() { var result = RunAdbCommand("version"); result.ThrowIfFailed("Failed to get ADB version"); return result.StandardOutput; } public string GetAdbState() => RunAdbCommand("get-state").StandardOutput; public string RebootAndroidDevice() { var result = RunAdbCommand("reboot"); result.ThrowIfFailed("Failed to reboot the device"); return result.StandardOutput; } public void ClearAdbLog() { RunAdbCommand("logcat", "-b", "all", "-c"); // Android logs can unnecessarily hide log entries, so disable DisableChatty(); } public void EnableWifi(bool enable) => RunAdbCommand("shell", "svc", "wifi", enable ? "enable" : "disable") .ThrowIfFailed($"Failed to {(enable ? "enable" : "disable")} WiFi on the device"); public bool TryDumpAdbLog(string outputFilePath, string filterSpec = "") { // Workaround: Doesn't seem to have a flush() function and sometimes it doesn't have the full log on emulators. Thread.Sleep(3000); var result = RunAdbCommand(new[] { "logcat", "-d", filterSpec }, TimeSpan.FromMinutes(2)); if (result.ExitCode != 0) { // Could throw here, but it would tear down a possibly otherwise acceptable execution. _log.LogError($"Error getting ADB log:{Environment.NewLine}{result}"); return false; } else { Directory.CreateDirectory(Path.GetDirectoryName(outputFilePath) ?? throw new ArgumentNullException(nameof(outputFilePath))); File.WriteAllText(outputFilePath, result.StandardOutput); _log.LogInformation($"Wrote full ADB log ({CountLines(result.StandardOutput)} lines) to {outputFilePath}"); // Filter to only DOTNET-tagged lines for console output (full log is in the file above) var filteredLog = FilterToDotnetLines(result.StandardOutput); if (!string.IsNullOrEmpty(filteredLog)) { _log.LogInformation($"ADB log (DOTNET entries):{Environment.NewLine}{filteredLog}"); } else { _log.LogInformation("ADB log contained no DOTNET-tagged entries (see full log file for details)"); } return true; } } public static string FilterToDotnetLines(string logcatOutput) { if (string.IsNullOrEmpty(logcatOutput)) { return string.Empty; } var sb = new StringBuilder(); foreach (var line in logcatOutput.Split('\n')) { // Match lines from the DOTNET log tag used by the Mono Android test runner if (line.Contains(" DOTNET ") || line.Contains(" DOTNET: ")) { sb.AppendLine(line.TrimEnd('\r')); } } return sb.ToString().TrimEnd(); } private static int CountLines(string text) { if (string.IsNullOrEmpty(text)) { return 0; } int count = 0; for (int i = 0; i < text.Length; i++) { if (text[i] == '\n') { count++; } } return count + 1; } public string DumpBugReport(string outputFilePathWithoutFormat) { var reportManager = AdbReportFactory.CreateReportManager(_log, GetDeviceApiVersion()); return reportManager.DumpBugReport(this, outputFilePathWithoutFormat); } public int GetDeviceApiVersion() { if (_activeDevice?.ApiVersion != null) { return _activeDevice.ApiVersion.Value; } string? output = GetDeviceProperty(AdbProperty.ApiVersion, _activeDevice?.DeviceSerial); if (output == null) { throw new Exception("Failed to get device's API version"); } var apiVersion = int.Parse(output); if (_activeDevice != null) { _activeDevice.ApiVersion = apiVersion; } return apiVersion; } private static int? GetSettingValue (string value) { if (int.TryParse(value, out int result)) { return result; } else { return null; } } private void VerifyPackageVerificationSettingValue(string settingName, int expectedValue) { var result = RunAdbCommand(new[] { "shell", "settings", "get", "global", settingName }); var settingValue = GetSettingValue(result.StandardOutput.Trim()); _log.LogDebug($"{settingName} = {settingValue}"); if (settingValue != expectedValue) _log.LogWarning($"Installing debug apks on a device might be rejected with INSTALL_FAILED_VERIFICATION_FAILURE. Make sure to set '{settingName}' to '{expectedValue}'"); } public void CheckPackageVerificationSettings() { _log.LogDebug("Check current adb install and/or package verification settings"); VerifyPackageVerificationSettingValue("verifier_verify_adb_installs", expectedValue: 0); VerifyPackageVerificationSettingValue("package_verifier_enable", expectedValue: 0); } public bool WaitForDevice() { // This command waits for ANY kind of device to be available (emulator or real) // Needed because emulators start up asynchronously and take a while. // (Returns instantly if device is ready) // This can fail if _currentDevice is unset if there are multiple devices. _log.LogInformation("Waiting for device to be available (max 5 minutes)"); RunAdbCommand(new[] { "wait-for-device" }, TimeSpan.FromMinutes(5)) .ThrowIfFailed("Error waiting for Android device/emulator"); // Some users will be installing the emulator and immediately calling xharness, they need to be able to expect the device is ready to load APKs. // Once wait-for-device returns, we'll give it up to TimeToWaitForBootCompletion (default 5 min) for 'adb shell getprop sys.boot_completed' // to be '1' (as opposed to empty) to make subsequent automation happy. var watch = Stopwatch.StartNew(); bool bootCompleted = Retry( () => { string? result = GetDeviceProperty(AdbProperty.BootCompletion, _activeDevice?.DeviceSerial); _log.LogDebug($"sys.boot_completed = '{result}'"); return result?.StartsWith('1') ?? false; }, retryInterval: TimeSpan.FromSeconds(10), retryPeriod: TimeToWaitForBootCompletion); if (bootCompleted) { _log.LogDebug($"Waited {(int)watch.Elapsed.TotalSeconds} seconds for device boot completion"); return true; } else { _log.LogError($"Did not detect boot completion variable on device; device may be in a bad state"); return false; } } public void StartAdbServer() { bool started = Retry( () => { var result = RunAdbCommand(new[] { "start-server" }, TimeSpan.FromMinutes(1)); started = result.Succeeded; if (!started) { _log.LogWarning($"Error starting the ADB server" + Environment.NewLine + result); try { KillAdbServer(); } catch (Exception e) { _log.LogError($"Error killing ADB server after a failed start: {e.Message}"); } } else { _log.LogDebug(result.StandardOutput); } return started; }, retryInterval: TimeSpan.FromSeconds(10), retryPeriod: TimeSpan.FromMinutes(5)); if (!started) { throw new AdbFailureException("Failed to start the ADB server"); } } public void KillAdbServer() => RunAdbCommand(new[] { "kill-server" }).ThrowIfFailed("Error killing ADB Server"); /// /// Attempts to recover the Android emulator when no suitable device is found. /// Steps: /// 1. Logs diagnostics about currently available devices /// 2. Resets the ADB daemon (kill-server + start-server) /// 3. If device still not visible, tries to restart the emulator process via /// systemctl (on Linux/systemd machines) or 'adb emu restart' as a fallback /// 4. Waits for the device to appear and complete boot /// /// true if a device appeared after recovery, false otherwise public bool TryRecoverEmulator() { // Step 1: Diagnostics - log what devices ARE currently attached _log.LogInformation("Attempting emulator recovery. Logging available devices for diagnostics..."); var devicesResult = RunAdbCommand(new[] { "devices", "-l" }, TimeSpan.FromSeconds(30)); _log.LogInformation($"Current 'adb devices -l' output:{Environment.NewLine}{devicesResult.StandardOutput}"); // On Linux, also log the emulator service status for diagnostics if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { try { var statusResult = RunSystemCommand("systemctl", new[] { "status", "android-emulator" }, TimeSpan.FromSeconds(15)); _log.LogInformation($"systemctl status android-emulator:{Environment.NewLine}{statusResult.StandardOutput}"); } catch (Exception e) { _log.LogDebug($"Unable to check systemctl status: {e.Message}"); } } // Step 2: Reset ADB daemon (kill-server + start-server) _log.LogInformation("Resetting ADB server as part of emulator recovery..."); try { KillAdbServer(); } catch (Exception e) { _log.LogWarning($"Error killing ADB server during recovery: {e.Message}"); } Thread.Sleep(TimeSpan.FromSeconds(2)); try { StartAdbServer(); } catch (Exception e) { _log.LogWarning($"Error restarting ADB server during recovery: {e.Message}"); } // Step 3: Re-check for devices after ADB reset - if a device reappeared, we are done var recheckResult = RunAdbCommand(new[] { "devices", "-l" }, TimeSpan.FromSeconds(30)); _log.LogInformation($"Devices after ADB server reset:{Environment.NewLine}{recheckResult.StandardOutput}"); var recheckLines = recheckResult.StandardOutput.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); if (recheckLines.Length >= 2) { _log.LogInformation("Device(s) reappeared after ADB server reset; skipping emulator service restart"); return true; } // Step 4: Try to restart the emulator process bool restartAttempted = false; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { try { _log.LogInformation("Attempting to restart android-emulator service via systemctl..."); var restartResult = RunSystemCommand("systemctl", new[] { "restart", "android-emulator" }, TimeSpan.FromMinutes(2)); if (restartResult.Succeeded) { _log.LogInformation("systemctl restart android-emulator succeeded"); restartAttempted = true; } else { _log.LogWarning($"systemctl restart android-emulator failed (exit code {restartResult.ExitCode}):{Environment.NewLine}{restartResult.StandardError}"); } } catch (Exception e) { _log.LogDebug($"Unable to restart via systemctl: {e.Message}"); } } if (!restartAttempted) { // Fallback: attempt 'adb emu restart' (works if emulator console port is reachable) _log.LogInformation("Attempting 'adb emu restart' as emulator restart fallback..."); try { var emuResult = RunAdbCommand(new[] { "emu", "restart" }, TimeSpan.FromSeconds(30)); _log.LogDebug($"adb emu restart output: {emuResult.StandardOutput}"); restartAttempted = true; } catch (Exception e) { _log.LogWarning($"adb emu restart failed: {e.Message}"); } } if (!restartAttempted) { _log.LogWarning("No emulator restart method succeeded; recovery not possible"); return false; } // Step 5: Wait for a device to reappear _log.LogInformation("Waiting for emulator to reappear after recovery (max 5 minutes)..."); bool deviceAppeared = Retry( () => { var r = RunAdbCommand(new[] { "devices", "-l" }, TimeSpan.FromSeconds(30)); var lines = r.StandardOutput.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); return lines.Length >= 2; }, retryInterval: TimeSpan.FromSeconds(10), retryPeriod: TimeSpan.FromMinutes(5)); if (!deviceAppeared) { _log.LogWarning("No device appeared after emulator recovery attempt"); return false; } // Step 6: Wait for boot completion _log.LogInformation("Waiting for emulator boot completion after recovery..."); var bootWatch = Stopwatch.StartNew(); bool bootCompleted = Retry( () => { string? bootStatus = GetDeviceProperty(AdbProperty.BootCompletion, null); _log.LogDebug($"sys.boot_completed = '{bootStatus}'"); return bootStatus?.StartsWith('1') ?? false; }, retryInterval: TimeSpan.FromSeconds(10), retryPeriod: TimeToWaitForBootCompletion); if (bootCompleted) { _log.LogInformation($"Emulator recovered and boot completed after {(int)bootWatch.Elapsed.TotalSeconds} seconds"); return true; } else { _log.LogWarning("Emulator appeared but did not complete boot within the timeout after recovery"); return false; } } /// /// Runs a non-ADB system command (e.g. systemctl) and returns the result. /// private static ProcessExecutionResults RunSystemCommand(string command, IEnumerable arguments, TimeSpan timeout) { var processStartInfo = new ProcessStartInfo() { CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, FileName = command, }; foreach (var arg in arguments) { processStartInfo.ArgumentList.Add(arg); } var p = new Process() { StartInfo = processStartInfo }; var standardOut = new StringBuilder(); var standardErr = new StringBuilder(); p.OutputDataReceived += (sender, e) => { if (e.Data != null) lock (standardOut) standardOut.AppendLine(e.Data); }; p.ErrorDataReceived += (sender, e) => { if (e.Data != null) lock (standardErr) standardErr.AppendLine(e.Data); }; p.Start(); p.BeginOutputReadLine(); p.BeginErrorReadLine(); bool timedOut = false; int exitCode; if (!p.WaitForExit((int)Math.Min(timeout.TotalMilliseconds, int.MaxValue))) { timedOut = true; exitCode = -1; try { p.Kill(); } catch { } } else { p.WaitForExit(); exitCode = p.ExitCode; } p.Close(); lock (standardOut) lock (standardErr) { return new ProcessExecutionResults() { ExitCode = exitCode, StandardOutput = standardOut.ToString(), StandardError = standardErr.ToString(), TimedOut = timedOut, }; } } public int CopyHeadlessFolder(string testPath, bool sharedRuntime = false) { _log.LogInformation($"Attempting to install {testPath}"); if (string.IsNullOrEmpty(testPath)) { throw new ArgumentException($"No value supplied for {nameof(testPath)} "); } if (!Directory.Exists(testPath)) { throw new FileNotFoundException($"Could not find {testPath}", testPath); } var targetDirectory = GlobalReadWriteDirectory + Path.AltDirectorySeparatorChar + new DirectoryInfo(testPath).Name; if (sharedRuntime) { targetDirectory = GlobalReadWriteDirectory + Path.AltDirectorySeparatorChar + "runtime"; } var result = RunAdbCommand(new[] { "push", testPath, targetDirectory }); // Two possible retry scenarios, theoretically both can happen on the same run: // 1. Pipe between ADB server and emulator device is broken; restarting the ADB server helps if (result.ExitCode == (int)AdbExitCodes.ADB_BROKEN_PIPE || result.StandardError.Contains(AdbInstallBrokenPipeError)) { _log.LogWarning($"Hit broken pipe error; Will make one attempt to restart ADB server, then retry the install"); KillAdbServer(); StartAdbServer(); result = RunAdbCommand(new[] { "push", testPath, targetDirectory }); } // 2. Installation cache on device is messed up; restarting the device reliably seems to unblock this (unless the device is actually full, if so this will error the same) if (result.ExitCode != (int)AdbExitCodes.SUCCESS && result.StandardError.Contains(AdbDeviceFullInstallFailureMessage)) { var firstAttemptResult = result; _log.LogWarning($"It seems the package installation cache may be full on the device. We'll try to reboot it before trying one more time."); RebootAndroidDevice(); WaitForDevice(); result = RunAdbCommand(new[] { "push", testPath, targetDirectory }); // Only log the initial failure output if the retry also failed if (result.ExitCode != (int)AdbExitCodes.SUCCESS) { _log.LogWarning($"Initial failure output:{Environment.NewLine}{firstAttemptResult}"); } } // 3. Installation timed out or failed with exception; restarting the ADB server, reboot the device and give more time for installation // installer might hang up so we need to clean it up and free memory if (result.ExitCode == (int)AdbExitCodes.INSTRUMENTATION_TIMEOUT || (result.ExitCode != (int)AdbExitCodes.SUCCESS && result.StandardError.Contains(AdbInstallException))) { _log.LogWarning($"Installation failed; Will make one attempt to restart ADB server and the device, then retry the install"); KillAdbServer(); StartAdbServer(); RebootAndroidDevice(); WaitForDevice(); result = RunAdbCommand(new[] { "push", testPath, targetDirectory }, TimeSpan.FromMinutes(10)); } if (result.ExitCode != 0) { _log.LogError($"Error:{Environment.NewLine}{result}"); } else { _log.LogInformation($"Successfully installed {testPath} to {targetDirectory}"); } return result.ExitCode; } public int InstallApk(string apkPath) { _log.LogInformation($"Attempting to install {apkPath}"); if (string.IsNullOrEmpty(apkPath)) { throw new ArgumentException($"No value supplied for {nameof(apkPath)} "); } if (!File.Exists(apkPath)) { throw new FileNotFoundException($"Could not find {apkPath}", apkPath); } var result = RunAdbCommand(new[] { "install", apkPath }); // Two possible retry scenarios, theoretically both can happen on the same run: // 1. Pipe between ADB server and emulator device is broken; restarting the ADB server helps if (result.ExitCode == (int)AdbExitCodes.ADB_BROKEN_PIPE || result.StandardError.Contains(AdbInstallBrokenPipeError)) { _log.LogWarning($"Hit broken pipe error; Will make one attempt to restart ADB server, then retry the install"); KillAdbServer(); StartAdbServer(); result = RunAdbCommand(new[] { "install", apkPath }); } // 2. Installation cache on device is messed up; restarting the device reliably seems to unblock this (unless the device is actually full, if so this will error the same) if (result.ExitCode != (int)AdbExitCodes.SUCCESS && result.StandardError.Contains(AdbDeviceFullInstallFailureMessage)) { var firstAttemptResult = result; _log.LogWarning($"It seems the package installation cache may be full on the device. We'll try to reboot it before trying one more time."); RebootAndroidDevice(); WaitForDevice(); result = RunAdbCommand(new[] { "install", apkPath }); // Only log the initial failure output if the retry also failed if (result.ExitCode != (int)AdbExitCodes.SUCCESS) { _log.LogWarning($"Initial failure output:{Environment.NewLine}{firstAttemptResult}"); } } // 3. Installation timed out or failed with exception; restarting the ADB server, reboot the device and give more time for installation // installer might hang up so we need to clean it up and free memory if (result.ExitCode == (int)AdbExitCodes.INSTRUMENTATION_TIMEOUT || (result.ExitCode != (int)AdbExitCodes.SUCCESS && result.StandardError.Contains(AdbInstallException))) { _log.LogWarning($"Installation failed; Will make one attempt to restart ADB server and the device, then retry the install"); KillAdbServer(); StartAdbServer(); RebootAndroidDevice(); WaitForDevice(); result = RunAdbCommand(new[] { "install", apkPath }, TimeSpan.FromMinutes(10)); } if (result.ExitCode != 0) { _log.LogError($"Error:{Environment.NewLine}{result}"); } else { _log.LogInformation($"Successfully installed {apkPath}"); } return result.ExitCode; } public int DeleteHeadlessFolder(string testPath) { if (string.IsNullOrEmpty(testPath)) { throw new ArgumentNullException(nameof(testPath)); } var fullTestPath = GlobalReadWriteDirectory + Path.AltDirectorySeparatorChar + new DirectoryInfo(testPath).Name; _log.LogInformation($"Attempting to remove folder '{fullTestPath}'.."); var result = RunAdbCommand(new[] { "shell", "rm", "-fr", fullTestPath }); // See note above in install() if (result.ExitCode == (int)AdbExitCodes.ADB_BROKEN_PIPE) { _log.LogWarning($"Hit broken pipe error; Will make one attempt to restart ADB server, and retry the uninstallation"); KillAdbServer(); StartAdbServer(); result = RunAdbCommand(new[] { "shell", "rm", "-fr", fullTestPath }); } if (result.ExitCode == (int)AdbExitCodes.SUCCESS) { _log.LogInformation($"Successfully uninstalled {fullTestPath}"); } else { _log.LogError(message: $"Failed to uninstall {fullTestPath}: {result}"); } return result.ExitCode; } public int UninstallApk(string apkName) { if (string.IsNullOrEmpty(apkName)) { throw new ArgumentNullException(nameof(apkName)); } _log.LogInformation($"Attempting to remove apk '{apkName}'.."); var result = RunAdbCommand(new[] { "uninstall", apkName }); // See note above in install() if (result.ExitCode == (int)AdbExitCodes.ADB_BROKEN_PIPE) { _log.LogWarning($"Hit broken pipe error; Will make one attempt to restart ADB server, and retry the uninstallation"); KillAdbServer(); StartAdbServer(); result = RunAdbCommand(new[] { "uninstall", apkName }); } if (result.ExitCode == (int)AdbExitCodes.SUCCESS) { _log.LogInformation($"Successfully uninstalled {apkName}"); } else if (result.ExitCode == (int)AdbExitCodes.ADB_UNINSTALL_APP_NOT_ON_DEVICE || result.ExitCode == (int)AdbExitCodes.ADB_UNINSTALL_APP_NOT_ON_EMULATOR) { _log.LogInformation($"APK '{apkName}' was not on device"); } else { _log.LogError(message: $"Error: {result}"); } return result.ExitCode; } // This function works but given we'll likely only be using Instrumentations doesn't matter. public int KillApk(string apkName) { _log.LogInformation($"Killing all running processes for '{apkName}': "); var result = RunAdbCommand(new[] { "shell", "am", "kill", "--user", "all", apkName }); if (result.ExitCode != (int)AdbExitCodes.SUCCESS) { _log.LogError($"Error:{Environment.NewLine}{result}"); } else { _log.LogDebug($"Success!{Environment.NewLine}{result.StandardOutput}"); } return result.ExitCode; } public int KillProcess(string testName) { _log.LogInformation($"Killing all running processes for '{testName}': "); var result = RunAdbCommand(new[] { "shell", "pkill", testName }); if (result.ExitCode != (int)AdbExitCodes.SUCCESS) { _log.LogError($"Failed to kill process by name ({testName}):{Environment.NewLine}{result}"); } else { _log.LogDebug($"Process {testName} killed!{Environment.NewLine}{result.StandardOutput}"); } return result.ExitCode; } // Assumes the directory is empty so any files present after the pull are new. public List PullFiles(string apkPackageName, string devicePath, string localPath) { if (string.IsNullOrEmpty(localPath)) { throw new ArgumentNullException(nameof(localPath)); } string tempFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); try { Directory.CreateDirectory(tempFolder); Directory.CreateDirectory(localPath); _log.LogInformation($"Attempting to pull contents of {devicePath} to {localPath}"); var copiedFiles = new List(); var result = RunAdbCommand(new[] { "pull", devicePath, tempFolder }); if (result.ExitCode != (int)AdbExitCodes.SUCCESS) { if (GetDeviceApiVersion() != 30) { throw new AdbFailureException($"Failed pulling files: {result}"); } // On Android API 30 we can't use "adb pull" directly due to permission issues on emulators, see https://github.com/dotnet/xharness/issues/385 // As a workaround we copy the files to the temp directory on the device using "run-as" and pull from there _log.LogInformation($"Failed to pull file. Device is running Android API 30, trying fallback to pull {devicePath}"); result = RunAdbCommand(new[] { "shell", "run-as", apkPackageName, "ls", devicePath }); result.ThrowIfFailed($"Failed checking for file using fallback: {result}"); string? fileName = devicePath.Split("/").LastOrDefault(); if (string.IsNullOrWhiteSpace(fileName)) { throw new AdbFailureException($"Failed pulling file using fallback: Couldn't determine filename for {devicePath}"); } string deviceTempPath = $"/data/local/tmp/{fileName}"; result = RunAdbCommand(new[] { "shell", "rm", "-rf", deviceTempPath }); result.ThrowIfFailed($"Failed removing {deviceTempPath} before using fallback: {result}"); result = RunAdbCommand(new[] { "shell", "touch", deviceTempPath }); result.ThrowIfFailed($"Failed touching {deviceTempPath}: {result}"); result = RunAdbCommand(new[] { "shell", "run-as", apkPackageName, "cp", devicePath, deviceTempPath }); result.ThrowIfFailed($"Failed copying file using fallback: {result}"); result = RunAdbCommand(new[] { "pull", deviceTempPath, tempFolder }); result.ThrowIfFailed($"Failed pulling file using fallback: {result}"); result = RunAdbCommand(new[] { "shell", "rm", "-f", deviceTempPath }); result.ThrowIfFailed($"Failed removing {deviceTempPath} after using fallback: {result}"); } var copiedToTemp = Directory.GetFiles(tempFolder, "*", SearchOption.AllDirectories); foreach (var filePath in copiedToTemp) { var relativePath = Path.GetRelativePath(tempFolder, filePath); var destinationPath = Path.Combine(localPath, relativePath); // if the file is already there, just warn and skip it. if (File.Exists(destinationPath)) { _log.LogWarning($"Skipping file copy as {destinationPath} already exists"); } else { Directory.CreateDirectory(Path.GetDirectoryName(destinationPath) ?? throw new ArgumentException(nameof(destinationPath))); File.Move(filePath, destinationPath); copiedFiles.Add(destinationPath); } } _log.LogDebug($"Copied {copiedFiles.Count} files to {localPath}"); return copiedFiles; } finally { Directory.Delete(tempFolder, true); } } // Assumes the directory is empty so any files present after the pull are new. public int HeadlessPullFiles(string devicePath, string localPath) { if (string.IsNullOrEmpty(localPath)) { throw new ArgumentNullException(nameof(localPath)); } Directory.CreateDirectory(localPath); _log.LogInformation($"Attempting to pull contents of {devicePath} to {localPath}"); var result = RunAdbCommand(new[] { "pull", devicePath, localPath }); if (result.ExitCode != (int)AdbExitCodes.SUCCESS) { _log.LogError($"Failed to pull file."); } return (int)AdbExitCodes.SUCCESS; } /// /// Gets all attached devices and their properties. /// public IReadOnlyCollection GetDevices() => GetDevices( AdbProperty.Architecture, AdbProperty.ApiVersion, AdbProperty.SupportedArchitectures); /// /// Gets all connected devices that satisfy the requirements. /// /// Should we also query device's architecture? /// Should we also query device's architecture? /// Specifies a particular device we are looking for /// Filters devices based on the API (SDK) level/version /// Allows only devices that support at least one of given architectures /// Allows only devices with a given app installed /// List of devices that satisfy the requirements public AndroidDevice? GetDevice( bool loadArchitecture = false, bool loadApiVersion = false, string? requiredDeviceId = null, int? requiredApiVersion = null, IEnumerable? requiredArchitectures = null, string? requiredInstalledApp = null) => GetDevice( singleDevice: false, loadArchitecture, loadApiVersion, requiredDeviceId, requiredApiVersion, requiredArchitectures, requiredInstalledApp); /// /// Gets all connected devices that satisfy the requirements. /// /// Should we also query device's architecture? /// Should we also query device's architecture? /// Specifies a particular device we are looking for /// Filters devices based on the API (SDK) level/version /// Allows only devices that support at least one of given architectures /// Allows only devices with a given app installed /// List of devices that satisfy the requirements public AndroidDevice? GetSingleDevice( bool loadArchitecture = false, bool loadApiVersion = false, string? requiredDeviceId = null, int? requiredApiVersion = null, IEnumerable? requiredArchitectures = null, string? requiredInstalledApp = null) => GetDevice( singleDevice: true, loadArchitecture, loadApiVersion, requiredDeviceId, requiredApiVersion, requiredArchitectures, requiredInstalledApp); /// /// Gets all connected devices that satisfy the requirements. /// /// Specifies a particular device we are looking for /// Filters devices based on the API (SDK) level/version /// Allows only devices that support at least one of given architectures /// Allows only devices with a given app installed /// List of devices that satisfy the requirements private IReadOnlyCollection GetAllDevices( string? requiredDeviceId = null, int? requiredApiVersion = null, IEnumerable? requiredArchitectures = null, string? requiredInstalledApp = null) { var properties = new List(); if (requiredApiVersion.HasValue) { properties.Add(AdbProperty.ApiVersion); } if (requiredArchitectures?.Any() ?? false) { properties.Add(AdbProperty.SupportedArchitectures); } if (requiredInstalledApp != null) { properties.Add(AdbProperty.InstalledApps); } IReadOnlyCollection devices; try { devices = GetDevices(properties.ToArray()); } catch (Exception toLog) { _log.LogError(toLog, $"Exception thrown while trying to find compatible device"); return Array.Empty(); } if (devices.Count == 0) { return Array.Empty(); } if (requiredDeviceId != null) { devices = devices.Where(device => device.DeviceSerial == requiredDeviceId).ToList(); if (devices.Count == 0) { _log.LogError($"No attached device with ID {requiredDeviceId} found"); return devices; } } if (requiredApiVersion != null) { devices = devices.Where(device => device.ApiVersion == requiredApiVersion).ToList(); if (devices.Count == 0) { _log.LogError($"No attached device with API {requiredApiVersion} detected"); return devices; } } if (requiredArchitectures?.Any() ?? false) { var availableDevices = devices; devices = devices.Where(device => device.SupportedArchitectures?.Intersect(requiredArchitectures).Any() ?? false).ToList(); if (devices.Count == 0) { var availableArchInfo = string.Join(", ", availableDevices.Select( d => $"{d.DeviceSerial}=[{string.Join(",", d.SupportedArchitectures ?? Array.Empty())}]")); _log.LogError($"No attached device supports one of required architectures: {string.Join(", ", requiredArchitectures)}. " + $"Found {availableDevices.Count} device(s) with: {availableArchInfo}"); return devices; } } if (requiredInstalledApp != null) { if (requiredInstalledApp.StartsWith("package:")) { devices = devices.Where(device => device.InstalledApplications?.Any(app => app.Contains(requiredInstalledApp)) ?? false).ToList(); if (devices.Count == 0) { _log.LogError($"No attached device with app {requiredInstalledApp} installed"); return devices; } } else if (requiredInstalledApp.StartsWith("filename:")) { devices = devices.Where(device => TestFileExists(requiredInstalledApp.Substring("filename:".Length), device.DeviceSerial)).ToList(); if (devices.Count == 0) { _log.LogError($"No attached device with file {requiredInstalledApp} installed"); return devices; } } else { _log.LogError($"Could not understand required app \"{requiredInstalledApp}\""); } } return devices; } private AndroidDevice? GetDevice( bool singleDevice, bool loadArchitecture = false, bool loadApiVersion = false, string? requiredDeviceId = null, int? requiredApiVersion = null, IEnumerable? requiredArchitectures = null, string? requiredInstalledApp = null) { var devices = GetAllDevices( requiredDeviceId, requiredApiVersion, requiredArchitectures, requiredInstalledApp); if (devices.Count == 0) { _log.LogDebug($"No suitable devices found"); if (singleDevice) { _log.LogError($"Cannot find a suitable device, please check that a device is attached"); } return null; } if (singleDevice && devices.Count > 1) { _log.LogError($"There is more than one suitable device. Please provide API version, device architecture or device ID"); return null; } var device = devices.First(); _log.LogDebug($"Found {devices.Count} possible devices. Using '{device.DeviceSerial}'"); SetActiveDevice(device); if (loadArchitecture && device.Architecture == null) { device.Architecture = GetDeviceProperty(AdbProperty.Architecture, device.DeviceSerial); } if (loadApiVersion && device.ApiVersion == null) { device.ApiVersion = GetDeviceApiVersion(); } return device; } private IReadOnlyCollection GetDevices(params AdbProperty[] propertiesToLoad) { string[] standardOutputLines = Array.Empty(); _log.LogInformation("Finding attached devices/emulators..."); // Retry up to 3 mins til we get output; if the ADB server isn't started the output will come from a child process and we'll miss it. ProcessExecutionResults result = Retry( action: () => RunAdbCommand(new[] { "devices", "-l" }, TimeSpan.FromSeconds(30)), needsRetry: r => { if (!r.Succeeded) { _log.LogDebug("Unexpected response from adb devices -l:" + Environment.NewLine + r); return true; } standardOutputLines = r.StandardOutput.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); // We will keep retrying until we get something back like 'List of devices attached...{newline} {info about a device} ', // which when split on newlines ignoring empties will be at least 2 lines when there are any available devices. if (standardOutputLines.Length < 2) { _log.LogDebug("No attached devices found" + Environment.NewLine + r); return true; } return false; }, retryInterval: TimeSpan.FromSeconds(10), retryPeriod: TimeSpan.FromSeconds(90)); result.ThrowIfFailed("Failed to enumerate attached devices"); // Two lines = At least one device was found. On a multi-device machine, we can't function without specifying device serial number. if (standardOutputLines.Length < 2) { // Abandon the run here, don't just guess. _log.LogWarning("No attached devices / emulators detected. " + "Check that any emulators have been started, and attached device(s) are connected via USB, powered-on, unlocked and authorized."); return Array.Empty(); } var devices = new List(); _log.LogDebug($"Found {standardOutputLines.Length - 1} possible devices"); // Start at 1 to skip first line, which is always 'List of devices attached' for (int lineNumber = 1; lineNumber < standardOutputLines.Length; lineNumber++) { _log.LogDebug($"Evaluating output line for device serial: {standardOutputLines[lineNumber]}"); var lineParts = standardOutputLines[lineNumber].Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var device = new AndroidDevice(lineParts[0]); foreach (var property in propertiesToLoad) { string? value = GetDeviceProperty(property, device.DeviceSerial); switch (property) { case AdbProperty.Architecture: device.Architecture = value; break; case AdbProperty.ApiVersion: device.ApiVersion = value == null ? null : int.Parse(value); break; case AdbProperty.SupportedArchitectures: device.SupportedArchitectures = value?.Split(new char[] { ',', '\r', '\n' }); break; case AdbProperty.InstalledApps: device.InstalledApplications = value?.Split("\n"); break; } } devices.Add(device); } return devices; } private string? GetDeviceProperty(AdbProperty property, string? deviceName = null) { IEnumerable args = s_commandList[property]; if (!string.IsNullOrEmpty(deviceName)) { args = new[] { "-s", deviceName }.Concat(args); } // Assumption: All Devices on a machine running Xharness should attempt to be online or disconnected. ProcessExecutionResults result = Retry( action: () => RunAdbCommand(args, TimeSpan.FromSeconds(30)), needsRetry: r => { if (!r.Succeeded || r.StandardError.Contains("device offline", StringComparison.OrdinalIgnoreCase)) { _log.LogWarning($"Device {deviceName} is offline; retrying up to five minutes"); return true; } return false; }, retryInterval: TimeSpan.FromSeconds(10), retryPeriod: TimeSpan.FromMinutes(5)); if (!result.Succeeded) { _log.LogError($"Failed to get device's property {property}. Check if a device is attached / emulator is started" + Environment.NewLine + result.StandardError); return null; } return result.StandardOutput.Trim(); } private bool TestFileExists(string path, string? deviceName = null) { var deviceTestPath = GlobalReadWriteDirectory + Path.AltDirectorySeparatorChar + new DirectoryInfo(path).Name; IEnumerable args = new string[] {"shell", "stat", deviceTestPath}; if (!string.IsNullOrEmpty(deviceName)) { args = new[] { "-s", deviceName }.Concat(args); } // Assumption: All Devices on a machine running Xharness should attempt to be online or disconnected. ProcessExecutionResults result = Retry( action: () => RunAdbCommand(args, TimeSpan.FromSeconds(30)), needsRetry: r => { if (r.StandardError.Contains("device offline", StringComparison.OrdinalIgnoreCase)) { _log.LogWarning($"Device {deviceName} is offline; retrying up to five minutes"); return true; } return false; }, retryInterval: TimeSpan.FromSeconds(10), retryPeriod: TimeSpan.FromMinutes(5)); if (!result.Succeeded) { _log.LogError($"Failed to check existence of {deviceTestPath}. Check if a device is attached / emulator is started" + Environment.NewLine + result.StandardError); return false; } return (result.ExitCode == 0); } public void SetActiveDevice(AndroidDevice? device) { _processManager.DeviceSerial = device?.DeviceSerial ?? string.Empty; _activeDevice = device; if (device is null) { _log.LogInformation($"Active Android device unset"); } else { _log.LogInformation($"Active Android device set to serial '{device.DeviceSerial}'"); } } public ProcessExecutionResults RunHeadlessCommand(string testPath, string runtimePath, string testAssembly, string testScript, TimeSpan timeout) { var deviceTestPath = GlobalReadWriteDirectory + Path.AltDirectorySeparatorChar + new DirectoryInfo(testPath).Name + Path.AltDirectorySeparatorChar + testScript; var deviceRuntimePath = GlobalReadWriteDirectory + Path.AltDirectorySeparatorChar + "runtime" + Path.AltDirectorySeparatorChar + "dotnet"; var adbArgs = new List { "shell", deviceTestPath, "-r", deviceRuntimePath, }; _log.LogInformation($"Setting executable permissions on {testScript} and runtime"); var result = RunAdbCommand(new[] { "shell", "chmod", "a+x", deviceTestPath, deviceRuntimePath }); result.ThrowIfFailed($"Failed setting permissions on {deviceTestPath} and {deviceRuntimePath}: {result}"); _log.LogInformation($"Starting {testScript} from {deviceTestPath} (exit code 0 == success)"); var stopWatch = Stopwatch.StartNew(); result = RunAdbCommand(adbArgs, timeout); stopWatch.Stop(); if (result.ExitCode != (int)AdbExitCodes.SUCCESS) { _log.LogInformation($"An error occurred running {testScript}"); } else { _log.LogInformation($"Running command {testScript} took {stopWatch.Elapsed.TotalSeconds} seconds"); } _log.LogDebug(result.ToString()); return result; } public ProcessExecutionResults RunApkInstrumentation(string apkName, string? instrumentationClassName, Dictionary args, TimeSpan timeout) { string displayName = string.IsNullOrEmpty(instrumentationClassName) ? "{default}" : instrumentationClassName; var adbArgs = new List { "shell", "am", "instrument" }; adbArgs.AddRange(args.SelectMany(arg => new[] { "-e", arg.Key, arg.Value })); adbArgs.Add("-w"); if (string.IsNullOrEmpty(instrumentationClassName)) { _log.LogInformation($"Starting default instrumentation class on {apkName} (exit code 0 == success)"); adbArgs.Add(apkName); } else { _log.LogInformation($"Starting instrumentation class '{instrumentationClassName}' on {apkName}"); adbArgs.Add($"{apkName}/{instrumentationClassName}"); } var stopWatch = Stopwatch.StartNew(); var result = RunAdbCommand(adbArgs, timeout); stopWatch.Stop(); if (result.ExitCode == (int)AdbExitCodes.INSTRUMENTATION_TIMEOUT) { _log.LogWarning("Running instrumentation class {name} timed out after waiting {seconds} seconds", displayName, stopWatch.Elapsed.TotalSeconds); } else { _log.LogInformation("Running instrumentation class {name} took {seconds} seconds", displayName, stopWatch.Elapsed.TotalSeconds); } _log.LogDebug(result.ToString()); return result; } private void DisableChatty() { var result = RunAdbCommand(new[] { "logcat", "-P", "'\"\"'" }, TimeSpan.FromMinutes(1)); if (!result.Succeeded) { _log.LogWarning($"Unable to disable chatty. Logcat may hide what it finds to be repeating entries."); } } #endregion #region Process runner helpers public ProcessExecutionResults RunAdbCommand(params string[] arguments) => RunAdbCommand(arguments, TimeSpan.FromMinutes(5)); public ProcessExecutionResults RunAdbCommand(IEnumerable arguments, TimeSpan timeOut) { if (!File.Exists(_absoluteAdbExePath)) { throw new FileNotFoundException($"Provided path for adb.exe was not valid ('{_absoluteAdbExePath}')", _absoluteAdbExePath); } return _processManager.Run(_absoluteAdbExePath, arguments, timeOut); } private bool Retry(Func action, TimeSpan retryInterval, TimeSpan retryPeriod) => Retry(action, result => !result, retryInterval, retryPeriod); private T Retry(Func action, Func needsRetry, TimeSpan retryInterval, TimeSpan retryPeriod) { var watch = Stopwatch.StartNew(); int attempt = 0; T result; while (true) { result = action(); if (!needsRetry(result)) { return result; } if (watch.Elapsed > retryPeriod) { _log.LogDebug($"All {attempt} retries of action failed"); break; } ++attempt; _log.LogDebug($"Attempt {attempt} failed, retrying in {(int)retryInterval.TotalSeconds} seconds..."); Thread.Sleep(retryInterval); } return result; } #endregion } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/AndroidDevice.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.Android; public record AndroidDevice { public string DeviceSerial { get; init; } public int? ApiVersion { get; set; } = null; public string? Architecture { get; set; } = null; public IReadOnlyCollection? SupportedArchitectures { get; set; } = null; public IReadOnlyCollection? InstalledApplications { get; set; } = null; public AndroidDevice(string deviceSerial) => DeviceSerial = deviceSerial; } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/ApkHelper.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; namespace Microsoft.DotNet.XHarness.Android; public static class ApkHelper { public static List GetApkSupportedArchitectures(string apkPath) { if (string.IsNullOrEmpty(apkPath)) { throw new ArgumentException("Please supply a value for apkPath"); } if (!File.Exists(apkPath)) { throw new FileNotFoundException($"Invalid APK Path: '{apkPath}'", apkPath); } if (!Path.GetExtension(apkPath).Equals(".apk", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Only know how to open APK files."); } using (ZipArchive archive = ZipFile.Open(apkPath, ZipArchiveMode.Read)) { // Enumerate all folders under /lib inside the zip var allLibFolders = archive.Entries.Where(e => e.FullName.StartsWith("lib/")) .Select(e => e.FullName[4..e.FullName.IndexOf('/', 4)]) .Distinct().ToList(); return allLibFolders; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/Execution/AdbProcessManager.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using Microsoft.DotNet.XHarness.Common.Utilities; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.Android.Execution; public class AdbProcessManager : IAdbProcessManager { private readonly ILogger _log; public AdbProcessManager(ILogger logger) => _log = logger; /// /// Whenever there are multiple devices attached to a system, most ADB commands will fail /// unless the specific device id is provided with -s {device serial #} /// public string DeviceSerial { get; set; } = string.Empty; public ProcessExecutionResults Run(string adbExePath, IEnumerable arguments, TimeSpan timeOut) { if (!string.IsNullOrEmpty(DeviceSerial)) { arguments = arguments.Prepend(DeviceSerial).Prepend("-s"); } var processStartInfo = new ProcessStartInfo() { CreateNoWindow = true, UseShellExecute = false, WorkingDirectory = Path.GetDirectoryName(adbExePath) ?? throw new ArgumentNullException(nameof(adbExePath)), RedirectStandardOutput = true, RedirectStandardError = true, FileName = adbExePath, }; foreach (var arg in arguments) { processStartInfo.ArgumentList.Add(arg); } _log.LogDebug($"Executing command: '{adbExePath} {StringUtils.FormatArguments(processStartInfo.ArgumentList)}'"); var p = new Process() { StartInfo = processStartInfo }; var standardOut = new StringBuilder(); var standardErr = new StringBuilder(); p.OutputDataReceived += delegate (object sender, DataReceivedEventArgs e) { lock (standardOut) { if (e.Data != null) { standardOut.AppendLine(e.Data); } } }; p.ErrorDataReceived += delegate (object sender, DataReceivedEventArgs e) { lock (standardErr) { if (e.Data != null) { standardErr.AppendLine(e.Data); } } }; p.Start(); p.BeginOutputReadLine(); p.BeginErrorReadLine(); bool timedOut = false; int exitCode; // (int.MaxValue ms is about 24 days). Large values are effectively timeouts for the outer harness if (!p.WaitForExit((int)Math.Min(timeOut.TotalMilliseconds, int.MaxValue))) { _log.LogError("Waiting for command timed out: execution may be compromised"); timedOut = true; exitCode = (int)AdbExitCodes.INSTRUMENTATION_TIMEOUT; // try to terminate the process try { p.Kill(); } catch (Exception e) { _log.LogError($"Failed to kill process: {e.Message}"); } } else { // we exited normally, call WaitForExit() again to ensure redirected standard output is processed p.WaitForExit(); exitCode = p.ExitCode; } p.Close(); lock (standardOut) lock (standardErr) { return new ProcessExecutionResults() { ExitCode = exitCode, StandardOutput = standardOut.ToString(), StandardError = standardErr.ToString(), TimedOut = timedOut }; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/Execution/AdbReportFactory.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.Android.Execution; internal class AdbReportFactory { // This method return proper ReportManager based on API number of current device // It allows to apply different logic for bugreport generation on API 21-23 and above internal static IReportManager CreateReportManager(ILogger log, int api) { if (api > 23) return new NewReportManager(log); else return new Api23AndOlderReportManager(log); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/Execution/Api23AndOlderReportManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Threading; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.Android.Execution; internal class Api23AndOlderReportManager : IReportManager { private readonly ILogger _log; public Api23AndOlderReportManager(ILogger log) { _log = log; } public string DumpBugReport(AdbRunner runner, string outputFilePathWithoutFormat) { // give some time for bug report to be available Thread.Sleep(3000); var result = runner.RunAdbCommand(new[] { "bugreport" }, TimeSpan.FromMinutes(5)); if (result.ExitCode != 0) { // Could throw here, but it would tear down a possibly otherwise acceptable execution. _log.LogError($"Error getting ADB bugreport:{Environment.NewLine}{result}"); return string.Empty; } else { File.WriteAllText($"{outputFilePathWithoutFormat}.txt", result.StandardOutput); _log.LogInformation($"Wrote ADB bugreport to {outputFilePathWithoutFormat}.txt"); return $"{outputFilePathWithoutFormat}.txt"; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/Execution/IAdbProcessManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Text; namespace Microsoft.DotNet.XHarness.Android.Execution; // At some point all the process management APIs should be unified. For now I just added an 's' to ProcessExecutionResult to prevent accidental collision public class ProcessExecutionResults { public bool TimedOut { get; set; } public int ExitCode { get; set; } public bool Succeeded => !TimedOut && ExitCode == 0; public string StandardOutput { get; set; } = ""; public string StandardError { get; set; } = ""; public void ThrowIfFailed(string failureMessage) { if (!Succeeded) { throw new AdbFailureException(failureMessage + Environment.NewLine + this); } } public override string ToString() { var output = new StringBuilder(); output.AppendLine($"Exit code: {ExitCode}"); output.AppendLine($"Std out:{Environment.NewLine}{StandardOutput}{Environment.NewLine}"); if (!string.IsNullOrEmpty(StandardError)) { output.AppendLine($"Std err:{Environment.NewLine}{StandardError}{Environment.NewLine}"); } return output.ToString(); } } /// /// Interface for calling the adb binary in a separate process. /// public interface IAdbProcessManager { public string DeviceSerial { get; set; } public ProcessExecutionResults Run(string filename, IEnumerable arguments, TimeSpan timeout); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/Execution/IReportManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.Android.Execution; internal interface IReportManager { string DumpBugReport(AdbRunner runner, string outputFilePathWithoutFormat); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/Execution/NewReportManager.cs ================================================ using System; using System.Threading; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.Android.Execution; class NewReportManager : IReportManager { private readonly ILogger _log; public NewReportManager(ILogger log) { _log = log; } public string DumpBugReport(AdbRunner runner, string outputFilePathWithoutFormat) { // give some time for bug report to be available Thread.Sleep(3000); var result = runner.RunAdbCommand(new[] { "bugreport", $"{outputFilePathWithoutFormat}.zip" }, TimeSpan.FromMinutes(5)); if (result.ExitCode != 0) { // Could throw here, but it would tear down a possibly otherwise acceptable execution. _log.LogError($"Error getting ADB bugreport:{Environment.NewLine}{result}"); return string.Empty; } else { _log.LogInformation($"Wrote ADB bugreport to {outputFilePathWithoutFormat}.zip"); return $"{outputFilePathWithoutFormat}.zip"; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/InstrumentationRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Android.Execution; using System.Collections.Generic; using System.IO; using System; using Microsoft.Extensions.Logging; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using System.Linq; namespace Microsoft.DotNet.XHarness.Android; public class InstrumentationRunner { public const string ReturnCodeVariableName = "return-code"; // nunit2 one should go away eventually private static readonly string[] s_xmlOutputVariableNames = { "nunit2-results-path", "test-results-path" }; private const string CoverageResultsPathVariableName = "coverage-results-path"; private const string TestRunSummaryVariableName = "test-execution-summary"; private const string ShortMessageVariableName = "shortMsg"; private const string ProcessCrashedShortMessage = "Process crashed"; private const string InstrumentationResultPrefix = "INSTRUMENTATION_RESULT:"; private readonly ILogger _logger; private readonly AdbRunner _runner; public InstrumentationRunner(ILogger logger, AdbRunner runner) { _logger = logger; _runner = runner; } public ExitCode RunApkInstrumentation( string apkPackageName, string? instrumentationName, Dictionary instrumentationArguments, string outputDirectory, string? deviceOutputFolder, TimeSpan timeout, int expectedExitCode) { int? instrumentationExitCode = null; var producedFiles = new List(); // No class name = default Instrumentation ProcessExecutionResults result = _runner.RunApkInstrumentation(apkPackageName, instrumentationName, instrumentationArguments, timeout); bool processCrashed = false; bool failurePullingFiles = false; bool logCatSucceeded; using (_logger.BeginScope("Post-test copy and cleanup")) { if (result.ExitCode == (int)ExitCode.SUCCESS) { (instrumentationExitCode, processCrashed, failurePullingFiles) = ParseInstrumentationResult(apkPackageName, outputDirectory, result.StandardOutput, producedFiles); } // Optionally copy off an entire folder if (!string.IsNullOrEmpty(deviceOutputFolder)) { try { var logs = _runner.PullFiles(apkPackageName, deviceOutputFolder, outputDirectory); foreach (string log in logs) { _logger.LogDebug($"Found output file: {log}"); producedFiles.Add(new DiagnosticsFile { Name = Path.GetFileName(log), Type = "device-output", Path = log, }); } } catch (Exception toLog) { _logger.LogError(toLog, "Hit error (typically permissions) trying to pull {filePathOnDevice}", deviceOutputFolder); failurePullingFiles = true; } } var logcatFileName = $"adb-logcat-{apkPackageName}-{(instrumentationName ?? "default")}.log"; var logcatFilePath = Path.Combine(outputDirectory, logcatFileName); logCatSucceeded = _runner.TryDumpAdbLog(logcatFilePath); if (logCatSucceeded) { producedFiles.Add(new DiagnosticsFile { Name = logcatFileName, Type = "logcat" }); } if (processCrashed) { var bugreportPath = _runner.DumpBugReport(Path.Combine(outputDirectory, $"adb-bugreport-{apkPackageName}")); if (!string.IsNullOrEmpty(bugreportPath)) { producedFiles.Add(new DiagnosticsFile { Name = Path.GetFileName(bugreportPath), Type = "bugreport" }); } } } // Determine exit code and emit summary after all operations complete ExitCode exitCode = DetermineExitCode(result, logCatSucceeded, processCrashed, failurePullingFiles, instrumentationExitCode, expectedExitCode); EmitRunSummary(exitCode, instrumentationExitCode, producedFiles, outputDirectory); return exitCode; } private ExitCode DetermineExitCode(ProcessExecutionResults result, bool logCatSucceeded, bool processCrashed, bool failurePullingFiles, int? instrumentationExitCode, int expectedExitCode) { if (!logCatSucceeded) { return ExitCode.SIMULATOR_FAILURE; } if (result.ExitCode == (int)AdbExitCodes.INSTRUMENTATION_TIMEOUT) { return ExitCode.TIMED_OUT; } if (processCrashed) { return ExitCode.APP_CRASH; } if (failurePullingFiles) { _logger.LogError($"Received expected instrumentation exit code ({instrumentationExitCode}), " + "but we hit errors pulling files from the device (see log for details.)"); return ExitCode.DEVICE_FILE_COPY_FAILURE; } if (!instrumentationExitCode.HasValue) { return ExitCode.RETURN_CODE_NOT_SET; } if (instrumentationExitCode != expectedExitCode) { _logger.LogError($"Non-success instrumentation exit code: {instrumentationExitCode}, expected: {expectedExitCode}"); return ExitCode.TESTS_FAILED; } return ExitCode.SUCCESS; } private (int? ExitCode, bool Crashed, bool FilePullFailed) ParseInstrumentationResult(string apkPackageName, string outputDirectory, string result, List producedFiles) { // This is where test instrumentation can communicate outwardly that test execution failed IReadOnlyDictionary resultValues = ParseInstrumentationOutputs(result); // Pull XUnit result XMLs off the device bool failurePullingFiles = PullResultXMLs(apkPackageName, outputDirectory, resultValues, producedFiles)!; bool processCrashed = false; // Pull coverage results if present (non-fatal on failure) if (resultValues.TryGetValue(CoverageResultsPathVariableName, out string? coveragePath)) { _logger.LogInformation($"Found coverage results file: '{coveragePath}'"); try { _runner.PullFiles(apkPackageName, coveragePath, outputDirectory); _logger.LogInformation($"Coverage results pulled to '{outputDirectory}'"); producedFiles.Add(new DiagnosticsFile { Name = Path.GetFileName(coveragePath), Type = "coverage", Path = Path.Combine(outputDirectory, Path.GetFileName(coveragePath)), }); } catch (Exception toLog) { _logger.LogWarning(toLog, "Failed to pull coverage results from {filePathOnDevice}. Coverage data may be unavailable.", coveragePath); } } if (resultValues.TryGetValue(TestRunSummaryVariableName, out string? testRunSummary)) { _logger.LogInformation($"Test execution summary:{Environment.NewLine}{testRunSummary}"); } if (resultValues.TryGetValue(ShortMessageVariableName, out string? shortMessage)) { _logger.LogInformation($"Short message:{Environment.NewLine}{shortMessage}"); processCrashed = shortMessage.Contains(ProcessCrashedShortMessage); } // Due to the particulars of how instrumentations work, ADB will report a 0 exit code for crashed instrumentations // We'll change that to a specific value and print a message explaining why. int? instrumentationExitCode = null; if (resultValues.TryGetValue(ReturnCodeVariableName, out string? returnCode)) { if (int.TryParse(returnCode, out int parsedExitCode)) { _logger.LogInformation($"Instrumentation finished normally with exit code {parsedExitCode}"); instrumentationExitCode = parsedExitCode; } else { _logger.LogError($"Un-parse-able value for '{ReturnCodeVariableName}' : '{returnCode}'"); } } else { _logger.LogError($"No value for '{ReturnCodeVariableName}' provided in instrumentation result. This may indicate a crashed test (see log)"); } return (ExitCode: instrumentationExitCode, Crashed: processCrashed, FilePullFailed: failurePullingFiles); } private bool PullResultXMLs(string apkPackageName, string outputDirectory, IReadOnlyDictionary resultValues, List producedFiles) { bool success = false; foreach (string possibleResultKey in s_xmlOutputVariableNames) { if (!resultValues.TryGetValue(possibleResultKey, out string? resultFile)) { continue; } _logger.LogInformation($"Found XML result file: '{resultFile}'(key: {possibleResultKey})"); try { _runner.PullFiles(apkPackageName, resultFile, outputDirectory); producedFiles.Add(new DiagnosticsFile { Name = Path.GetFileName(resultFile), Type = "test-results", Path = Path.Combine(outputDirectory, Path.GetFileName(resultFile)), }); } catch (Exception toLog) { _logger.LogError(toLog, "Hit error (typically permissions) trying to pull {filePathOnDevice}", resultFile); success = true; } } return success; } private IReadOnlyDictionary ParseInstrumentationOutputs(string stdout) { var outputs = new Dictionary(); string[] lines = stdout.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); foreach (string line in lines.Where(line => line.StartsWith(InstrumentationResultPrefix))) { var subString = line.Substring(InstrumentationResultPrefix.Length); string[] results = subString.Trim().Split('='); if (results.Length == 2) { var key = results[0]; var value = results[1]; if (outputs.ContainsKey(key)) { _logger.LogWarning($"Key '{key}' defined more than once"); outputs[key] = value; } else { outputs.Add(key, value); } } else { _logger.LogWarning($"Skipping output line due to key-value-pair parse failure: '{line}'"); } } return outputs; } private void EmitRunSummary(ExitCode exitCode, int? instrumentationExitCode, List producedFiles, string outputDirectory) { var device = _runner.GetActiveDevice(); string? deviceOsVersion = device?.ApiVersion.HasValue == true ? $"API {device.ApiVersion}" : null; RunSummaryEmitter.EmitRunSummary( _logger, exitCode, platform: "android", deviceName: device?.DeviceSerial, deviceOsVersion: deviceOsVersion, architecture: device?.Architecture, instrumentationExitCode: instrumentationExitCode, producedFiles: producedFiles); RunSummaryEmitter.WriteResultJsonFile( outputDirectory, exitCode, platform: "android", deviceName: device?.DeviceSerial, deviceOsVersion: deviceOsVersion, architecture: device?.Architecture, instrumentationExitCode: instrumentationExitCode, producedFiles: producedFiles); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Android/Microsoft.DotNet.XHarness.Android.csproj ================================================ $(XHarnessNetTFMs) ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/AppOperations/AppInstaller.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; namespace Microsoft.DotNet.XHarness.Apple; public interface IAppInstaller { Task InstallApp( AppBundleInformation appBundleInformation, TestTargetOs target, IDevice device, CancellationToken cancellationToken = default); } public class AppInstaller : IAppInstaller { private readonly IMlaunchProcessManager _processManager; private readonly ILog _mainLog; public AppInstaller(IMlaunchProcessManager processManager, ILog mainLog) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _mainLog = mainLog ?? throw new ArgumentNullException(nameof(mainLog)); } public async Task InstallApp( AppBundleInformation appBundleInformation, TestTargetOs target, IDevice device, CancellationToken cancellationToken = default) { if (!Directory.Exists(appBundleInformation.LaunchAppPath)) { throw new DirectoryNotFoundException("Failed to find the app bundle directory"); } var args = new MlaunchArguments(); if (target.Platform.IsSimulator()) { args.Add(new SimulatorUDIDArgument(device)); args.Add(new InstallAppOnSimulatorArgument(appBundleInformation.LaunchAppPath)); } else { args.Add(new DeviceNameArgument(device)); args.Add(new InstallAppOnDeviceArgument(appBundleInformation.LaunchAppPath)); if (target.Platform.IsWatchOSTarget()) { args.Add(new DeviceArgument("ios,watchos")); } } var totalSize = Directory.GetFiles(appBundleInformation.LaunchAppPath, "*", SearchOption.AllDirectories).Select((v) => new FileInfo(v).Length).Sum(); _mainLog.WriteLine($"Installing '{appBundleInformation.LaunchAppPath}' to '{device.Name}' ({totalSize / 1024.0 / 1024.0:N2} MB)"); return await _processManager.ExecuteCommandAsync(args, _mainLog, TimeSpan.FromMinutes(15), cancellationToken: cancellationToken); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/AppOperations/AppRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface IAppRunner { Task RunApp( AppBundleInformation appInformation, TestTargetOs target, IDevice device, IDevice? companionDevice, TimeSpan timeout, bool signalAppEnd, bool waitForExit, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, CancellationToken cancellationToken = default); Task RunMacCatalystApp( AppBundleInformation appInformation, TimeSpan timeout, bool signalAppEnd, bool waitForExit, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, CancellationToken cancellationToken = default); } /// /// Class that will run an app bundle on a given simulator/device and return the exit code. /// public class AppRunner : AppRunnerBase, IAppRunner { private readonly IMlaunchProcessManager _processManager; private readonly ICrashSnapshotReporterFactory _snapshotReporterFactory; private readonly IDeviceLogCapturerFactory _deviceLogCapturerFactory; private readonly IFileBackedLog _mainLog; private readonly ILogs _logs; private readonly IHelpers _helpers; public AppRunner( IMlaunchProcessManager processManager, ICrashSnapshotReporterFactory snapshotReporterFactory, ICaptureLogFactory captureLogFactory, IDeviceLogCapturerFactory deviceLogCapturerFactory, IFileBackedLog mainLog, ILogs logs, IHelpers helpers, Action? logCallback = null) : base(processManager, captureLogFactory, logs, mainLog, helpers, logCallback) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _snapshotReporterFactory = snapshotReporterFactory ?? throw new ArgumentNullException(nameof(snapshotReporterFactory)); _deviceLogCapturerFactory = deviceLogCapturerFactory ?? throw new ArgumentNullException(nameof(deviceLogCapturerFactory)); _mainLog = mainLog ?? throw new ArgumentNullException(nameof(mainLog)); _logs = logs ?? throw new ArgumentNullException(nameof(logs)); _helpers = helpers ?? throw new ArgumentNullException(nameof(helpers)); } public async Task RunMacCatalystApp( AppBundleInformation appInformation, TimeSpan timeout, bool signalAppEnd, bool waitForExit, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, CancellationToken cancellationToken = default) { _mainLog.WriteLine($"*** Executing '{appInformation.AppName}' on MacCatalyst ***"); var appOutputLog = _logs.Create(appInformation.BundleIdentifier + ".log", LogType.ApplicationLog.ToString(), timestamp: true); var envVariables = new Dictionary(); AddExtraEnvVars(envVariables, extraEnvVariables); if (signalAppEnd) { WatchForAppEndTag(out var appEndTag, ref appOutputLog, ref cancellationToken); envVariables.Add(EnviromentVariables.AppEndTag, appEndTag); } using (appOutputLog) { return await RunAndWatchForAppSignal(() => RunMacCatalystApp( appInformation, appOutputLog, timeout, waitForExit, extraAppArguments ?? Enumerable.Empty(), envVariables, cancellationToken)); } } public async Task RunApp( AppBundleInformation appInformation, TestTargetOs target, IDevice device, IDevice? companionDevice, TimeSpan timeout, bool signalAppEnd, bool waitForExit, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, CancellationToken cancellationToken = default) { ProcessExecutionResult result; ISimulatorDevice? simulator; ISimulatorDevice? companionSimulator; var isSimulator = target.Platform.IsSimulator(); using var crashLogs = new Logs(_logs.Directory); ICrashSnapshotReporter crashReporter = _snapshotReporterFactory.Create( _mainLog, crashLogs, isDevice: !isSimulator, device.Name); _mainLog.WriteLine($"*** Executing '{appInformation.AppName}' on {target.AsString()} '{device.Name}' ***"); if (isSimulator) { simulator = device as ISimulatorDevice; companionSimulator = companionDevice as ISimulatorDevice; if (simulator == null) { _mainLog.WriteLine("Didn't find any suitable simulator"); throw new NoDeviceFoundException(); } var mlaunchArguments = GetSimulatorArguments( appInformation, simulator, extraAppArguments, extraEnvVariables); result = await RunSimulatorApp( appInformation, mlaunchArguments, crashReporter, simulator, companionSimulator, timeout, waitForExit, cancellationToken); } else { var appOutputLog = _logs.Create(appInformation.BundleIdentifier + ".log", LogType.ApplicationLog.ToString(), timestamp: true); string? appEndTag = null; if (signalAppEnd) { WatchForAppEndTag(out appEndTag, ref appOutputLog, ref cancellationToken); } using (appOutputLog) { var mlaunchArguments = GetDeviceArguments( appInformation, device, waitForExit: waitForExit, isWatchTarget: target.Platform.IsWatchOSTarget(), extraAppArguments, extraEnvVariables, appEndTag); result = await RunDeviceApp( mlaunchArguments, crashReporter, device, appOutputLog, extraEnvVariables, timeout, cancellationToken); } } return result; } private async Task RunDeviceApp( MlaunchArguments mlaunchArguments, ICrashSnapshotReporter crashReporter, IDevice device, ILog appOutputLog, IEnumerable<(string, string?)> extraEnvVariables, TimeSpan timeout, CancellationToken cancellationToken) { using var deviceSystemLog = _logs.Create($"device-{device.Name}-{_helpers.Timestamp}.log", LogType.SystemLog.ToString()); using var deviceLogCapturer = _deviceLogCapturerFactory.Create(_mainLog, deviceSystemLog, device.UDID); deviceLogCapturer.StartCapture(); await crashReporter.StartCaptureAsync(); _mainLog.WriteLine("Starting the app"); var envVars = new Dictionary(); AddExtraEnvVars(envVars, extraEnvVariables); return await RunAndWatchForAppSignal(() => _processManager.ExecuteCommandAsync( mlaunchArguments, _mainLog, appOutputLog, appOutputLog, timeout, envVars, cancellationToken: cancellationToken)); } private static MlaunchArguments GetCommonArguments( IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, string? appEndTag) { var args = new MlaunchArguments(); // Arguments passed to the iOS app bundle args.AddRange(extraAppArguments.Select(arg => new SetAppArgumentArgument(arg))); args.AddRange(GetSetEnvVariableArguments(extraEnvVariables)); if (appEndTag != null) { args.Add(new SetEnvVariableArgument(EnviromentVariables.AppEndTag, appEndTag)); } return args; } private MlaunchArguments GetSimulatorArguments( AppBundleInformation appInformation, ISimulatorDevice simulator, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables) { var args = GetCommonArguments(extraAppArguments, extraEnvVariables, appEndTag: null); args.Add(new SimulatorUDIDArgument(simulator.UDID)); if (appInformation.Extension.HasValue) { switch (appInformation.Extension) { case Extension.TodayExtension: args.Add(new LaunchSimulatorExtensionArgument(appInformation.LaunchAppPath, appInformation.BundleIdentifier)); break; case Extension.WatchKit2: default: throw new NotImplementedException(); } } else { args.Add(new LaunchSimulatorBundleArgument(appInformation)); } return args; } private MlaunchArguments GetDeviceArguments( AppBundleInformation appInformation, IDevice device, bool waitForExit, bool isWatchTarget, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, string? appEndTag) { var args = GetCommonArguments(extraAppArguments, extraEnvVariables, appEndTag); args.Add(new DisableMemoryLimitsArgument()); args.Add(new DeviceNameArgument(device)); if (appInformation.Extension.HasValue) { switch (appInformation.Extension) { case Extension.TodayExtension: args.Add(new LaunchDeviceExtensionArgument(appInformation.LaunchAppPath, appInformation.BundleIdentifier)); break; case Extension.WatchKit2: default: throw new NotImplementedException(); } } else { args.Add(new LaunchDeviceBundleIdArgument(appInformation)); } if (isWatchTarget) { args.Add(new AttachNativeDebuggerArgument()); // this prevents the watch from backgrounding the app. } else if (waitForExit) { args.Add(new WaitForExitArgument()); } return args; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/AppOperations/AppRunnerBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.Common.Utilities; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public abstract class AppRunnerBase { private const string SystemLogPath = "/var/log/system.log"; private readonly IMlaunchProcessManager _processManager; private readonly ICaptureLogFactory _captureLogFactory; private readonly ILogs _logs; private readonly IHelpers _helpers; private readonly IFileBackedLog _mainLog; private bool _appEndSignalDetected = false; protected AppRunnerBase( IMlaunchProcessManager processManager, ICaptureLogFactory captureLogFactory, ILogs logs, IFileBackedLog mainLog, IHelpers helpers, Action? logCallback = null) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _captureLogFactory = captureLogFactory ?? throw new ArgumentNullException(nameof(captureLogFactory)); _logs = logs ?? throw new ArgumentNullException(nameof(logs)); _helpers = helpers ?? throw new ArgumentNullException(nameof(helpers)); if (logCallback == null) { _mainLog = mainLog ?? throw new ArgumentNullException(nameof(mainLog)); } else { // create using the main as the default log _mainLog = Log.CreateReadableAggregatedLog(mainLog, new CallbackLog(logCallback)); } } protected async Task RunMacCatalystApp( AppBundleInformation appInformation, ILog appOutputLog, TimeSpan timeout, bool waitForExit, IEnumerable extraArguments, Dictionary? environmentVariables, CancellationToken cancellationToken) { using var systemLog = _captureLogFactory.Create( path: _logs.CreateFile("MacCatalyst.system.log", LogType.SystemLog), systemLogPath: SystemLogPath, entireFile: false, LogType.SystemLog); // We need to make the binary executable var binaryPath = Path.Combine(appInformation.AppPath, "Contents", "MacOS", appInformation.BundleExecutable ?? appInformation.AppName); if (File.Exists(binaryPath)) { await _processManager.ExecuteCommandAsync("chmod", new[] { "+x", binaryPath }, _mainLog, TimeSpan.FromSeconds(10), cancellationToken: cancellationToken); } // On Big Sur it seems like the launch services database is not updated fast enough after we do some I/O with the app bundle. // Force registration for the app by running // /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f /path/to/app.app var lsRegisterPath = @"/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"; await _processManager.ExecuteCommandAsync(lsRegisterPath, new[] { "-f", appInformation.LaunchAppPath }, _mainLog, TimeSpan.FromSeconds(10), cancellationToken: cancellationToken); var arguments = new List { "-n" // Open a new instance of the application(s) even if one is already running. }; if (waitForExit) { // Wait until the applications exit (even if they were already open) arguments.Add("-W"); } arguments.Add(appInformation.LaunchAppPath); arguments.AddRange(extraArguments); systemLog.StartCapture(); var logStreamScanToken = CaptureMacCatalystLog(appInformation, cancellationToken); try { return await _processManager.ExecuteCommandAsync( "open", arguments, _mainLog, appOutputLog, appOutputLog, timeout, environmentVariables, cancellationToken); } finally { if (waitForExit) { systemLog.StopCapture(waitIfEmpty: TimeSpan.FromSeconds(10)); } // Stop scanning the logs logStreamScanToken.Cancel(); } } protected async Task RunSimulatorApp( AppBundleInformation appInformation, MlaunchArguments mlaunchArguments, ICrashSnapshotReporter crashReporter, ISimulatorDevice simulator, ISimulatorDevice? companionSimulator, TimeSpan timeout, bool waitForExit, CancellationToken cancellationToken) { _mainLog.WriteLine("System log for the '{1}' simulator is: {0}", simulator.SystemLog, simulator.Name); var simulatorLog = _captureLogFactory.Create( path: Path.Combine(_logs.Directory, simulator.Name + ".log"), systemLogPath: simulator.SystemLog, entireFile: false, LogType.SystemLog); simulatorLog.StartCapture(); _logs.Add(simulatorLog); var simulatorScanToken = await CaptureSimulatorLog(simulator, appInformation, cancellationToken); using var systemLogs = new DisposableList { simulatorLog }; if (companionSimulator != null) { _mainLog.WriteLine("System log for the '{1}' companion simulator is: {0}", companionSimulator.SystemLog, companionSimulator.Name); var companionLog = _captureLogFactory.Create( path: Path.Combine(_logs.Directory, companionSimulator.Name + ".log"), systemLogPath: companionSimulator.SystemLog, entireFile: false, LogType.CompanionSystemLog); companionLog.StartCapture(); _logs.Add(companionLog); systemLogs.Add(companionLog); var companionScanToken = await CaptureSimulatorLog(companionSimulator, appInformation, cancellationToken); if (companionScanToken != null) { simulatorScanToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, companionScanToken.Token); } } await crashReporter.StartCaptureAsync(); _mainLog.WriteLine("Launching the app"); // Create an application log to capture mlaunch output (including simctl launch --console output) // This is needed for exit code detection, as the app exit code (e.g., DOTNET.APP_EXIT_CODE) // is printed to stdout by the simulator and captured by mlaunch var appOutputLog = _logs.Create(appInformation.BundleIdentifier + ".log", LogType.ApplicationLog.ToString(), timestamp: true); if (waitForExit) { var result = await _processManager.ExecuteCommandAsync(mlaunchArguments, _mainLog, appOutputLog, appOutputLog, timeout, cancellationToken: cancellationToken); simulatorScanToken?.Cancel(); return result; } TaskCompletionSource appLaunched = new(); var scanLog = new ScanLog($"Launched {appInformation.BundleIdentifier} with pid", () => { _mainLog.WriteLine("App launch detected"); appLaunched.SetResult(); }); _mainLog.WriteLine("Waiting for the app to launch.."); var runTask = _processManager.ExecuteCommandAsync(mlaunchArguments, Log.CreateAggregatedLog(_mainLog, scanLog), appOutputLog, appOutputLog, timeout, cancellationToken: cancellationToken); await Task.WhenAny(runTask, appLaunched.Task); if (!appLaunched.Task.IsCompleted) { // In case the other task completes first, it is because one of these scenarios happened: // - The app crashed and never launched // - We missed the launch signal somehow and the app timed out // - The app launched and quit immediately and race condition noticed that before the scan log did its job // In all cases, we should return the result of the run task, it will be most likely 137 + Timeout (killed by us) // If not, it will be a success because the app ran for a super short amount of time _mainLog.WriteLine("App launch was not detected in time"); return runTask.Result; } _mainLog.WriteLine("Not waiting for the app to exit"); return new ProcessExecutionResult { ExitCode = 0 }; } /// /// User can pass additional arguments after the -- which get turned to environmental variables. /// /// Environmental variables where the arguments are added /// Variables to set protected void AddExtraEnvVars(Dictionary envVariables, IEnumerable<(string, string?)> variables) { using (var enumerator = variables.GetEnumerator()) { while (enumerator.MoveNext()) { var (name, value) = enumerator.Current; if (envVariables.ContainsKey(name)) { _mainLog.WriteLine($"Environmental variable {name} is already passed to the application to drive test run, skipping.."); continue; } envVariables[name] = value; } } } protected static IEnumerable GetSetEnvVariableArguments(IEnumerable<(string Name, string? Value)> envVariables) => envVariables .Where(pair => pair.Value is not null) .Select(pair => new SetEnvVariableArgument(pair.Name, pair.Value!)); protected static IEnumerable GetSetEnvVariableArguments(IEnumerable> envVariables) => GetSetEnvVariableArguments(envVariables.Select(pair => (pair.Key, pair.Value))); protected string WatchForAppEndTag( out string tag, ref IFileBackedLog appLog, ref CancellationToken cancellationToken) { tag = _helpers.GenerateGuid().ToString(); var appEndDetected = new CancellationTokenSource(); var appEndScanner = new ScanLog(tag, () => { _mainLog.WriteLine("Detected test end tag in application's output"); _appEndSignalDetected = true; appEndDetected.Cancel(); }); // We need to check for test end tag since iOS 14+ doesn't send the pidDiedCallback event to mlaunch appLog = Log.CreateReadableAggregatedLog(appLog, appEndScanner); cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, appEndDetected.Token).Token; return tag; } protected async Task RunAndWatchForAppSignal(Func> action) { var result = await action(); // When signal is detected, we cancel the call above via the cancellation token so we need to fix the result if (_appEndSignalDetected) { result.TimedOut = false; result.ExitCode = 0; } return result; } /// /// This method kicks off a process that scans a log stream coming from the running app and stores it into a log file. /// This method does not wait for the scan process to end but returns a cancellation token that can be used to cancel the scan. /// protected CancellationTokenSource CaptureMacCatalystLog(AppBundleInformation appInformation, CancellationToken cancellationToken) => CaptureLogStream(appInformation.BundleExecutable ?? appInformation.AppName, false, Array.Empty(), cancellationToken); /// /// This method kicks off a process that scans a log stream coming from the running app and stores it into a log file. /// This method does not wait for the scan process to end but returns a cancellation token that can be used to cancel the scan. /// protected async Task CaptureSimulatorLog( ISimulatorDevice simulator, AppBundleInformation appInformation, CancellationToken cancellationToken) { if (!await simulator.Boot(_mainLog, cancellationToken)) { _mainLog.WriteLine($"Failed to boot simulator {simulator.Name} in time! Exit code detection might fail"); } var appName = appInformation.BundleExecutable ?? appInformation.AppName; return CaptureLogStream(appName, true, new[] { "spawn", simulator.UDID, "log" }, cancellationToken); } private CancellationTokenSource CaptureLogStream(string appName, bool useSimctl, IEnumerable args, CancellationToken cancellationToken) { var logReadTokenSource = new CancellationTokenSource(); var log = _logs.Create($"{appName}.log", LogType.SystemLog.ToString(), timestamp: false); var logArgs = args.Concat(new[] { "stream", "--level=debug", "--color=none", "--style=compact", "--predicate", $"senderImagePath contains '{appName}'" }).ToArray(); _mainLog.WriteLine($"Scanning log stream for {appName} into '{log.FullPath}'.."); var streamCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, logReadTokenSource.Token).Token; if (useSimctl) { _processManager .ExecuteXcodeCommandAsync("simctl", logArgs, _mainLog, log, log, TimeSpan.FromDays(1), cancellationToken: streamCancellation) .DoNotAwait(); } else { _processManager .ExecuteCommandAsync("log", logArgs, _mainLog, log, log, TimeSpan.FromDays(1), cancellationToken: streamCancellation) .DoNotAwait(); } logReadTokenSource.Token.Register(log.Dispose); return logReadTokenSource; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/AppOperations/AppRunnerFactory.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface IAppRunnerFactory { IAppRunner Create(IFileBackedLog log, ILogs logs, Action? logCallback); } public class AppRunnerFactory : IAppRunnerFactory { private readonly IMlaunchProcessManager _processManager; private readonly ICrashSnapshotReporterFactory _snapshotReporterFactory; private readonly ICaptureLogFactory _captureLogFactory; private readonly IDeviceLogCapturerFactory _deviceLogCapturerFactory; private readonly IHelpers _helpers; public AppRunnerFactory( IMlaunchProcessManager processManager, ICrashSnapshotReporterFactory snapshotReporterFactory, ICaptureLogFactory captureLogFactory, IDeviceLogCapturerFactory deviceLogCapturerFactory, IHelpers helpers) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _snapshotReporterFactory = snapshotReporterFactory ?? throw new ArgumentNullException(nameof(snapshotReporterFactory)); _captureLogFactory = captureLogFactory ?? throw new ArgumentNullException(nameof(captureLogFactory)); _deviceLogCapturerFactory = deviceLogCapturerFactory ?? throw new ArgumentNullException(nameof(deviceLogCapturerFactory)); _helpers = helpers ?? throw new ArgumentNullException(nameof(helpers)); } public IAppRunner Create(IFileBackedLog log, ILogs logs, Action? logCallback) => new AppRunner(_processManager, _snapshotReporterFactory, _captureLogFactory, _deviceLogCapturerFactory, log, logs, _helpers, logCallback); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/AppOperations/AppTester.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.Common.Utilities; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Listeners; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface IAppTester { bool ListenerConnected { get; } Task<(TestExecutingResult Result, string ResultMessage)> TestApp( AppBundleInformation appInformation, TestTargetOs target, IDevice device, IDevice? companionDevice, TimeSpan timeout, TimeSpan testLaunchTimeout, bool signalAppEnd, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, XmlResultJargon xmlResultJargon = XmlResultJargon.xUnit, string[]? skippedMethods = null, string[]? skippedTestClasses = null, CancellationToken cancellationToken = default); Task<(TestExecutingResult Result, string ResultMessage)> TestMacCatalystApp( AppBundleInformation appInformation, TimeSpan timeout, TimeSpan testLaunchTimeout, bool signalAppEnd, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, XmlResultJargon xmlResultJargon = XmlResultJargon.xUnit, string[]? skippedMethods = null, string[]? skippedTestClasses = null, CancellationToken cancellationToken = default); } /// /// Class that will run an app bundle that contains the TestRunner on a given simulator/device. /// It will collect test results ran in the app and return results. /// public class AppTester : AppRunnerBase, IAppTester { private readonly IMlaunchProcessManager _processManager; private readonly ISimpleListenerFactory _listenerFactory; private readonly ICrashSnapshotReporterFactory _snapshotReporterFactory; private readonly IDeviceLogCapturerFactory _deviceLogCapturerFactory; private readonly ITestReporterFactory _testReporterFactory; private readonly IResultParser _resultParser; private readonly IFileBackedLog _mainLog; private readonly ILogs _logs; private readonly IHelpers _helpers; /// /// Denotes whether we had a successful connection over TCP during the run. /// This is used later to determine if a cause for a failed run is a failing TCP connection. /// public bool ListenerConnected { get; private set; } public AppTester( IMlaunchProcessManager processManager, ISimpleListenerFactory simpleListenerFactory, ICrashSnapshotReporterFactory snapshotReporterFactory, ICaptureLogFactory captureLogFactory, IDeviceLogCapturerFactory deviceLogCapturerFactory, ITestReporterFactory reporterFactory, IResultParser resultParser, IFileBackedLog mainLog, ILogs logs, IHelpers helpers, Action? logCallback = null) : base(processManager, captureLogFactory, logs, mainLog, helpers, logCallback) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _listenerFactory = simpleListenerFactory ?? throw new ArgumentNullException(nameof(simpleListenerFactory)); _snapshotReporterFactory = snapshotReporterFactory ?? throw new ArgumentNullException(nameof(snapshotReporterFactory)); _deviceLogCapturerFactory = deviceLogCapturerFactory ?? throw new ArgumentNullException(nameof(deviceLogCapturerFactory)); _testReporterFactory = reporterFactory ?? throw new ArgumentNullException(nameof(reporterFactory)); _resultParser = resultParser ?? throw new ArgumentNullException(nameof(resultParser)); _mainLog = mainLog ?? throw new ArgumentNullException(nameof(mainLog)); _logs = logs ?? throw new ArgumentNullException(nameof(logs)); _helpers = helpers ?? throw new ArgumentNullException(nameof(helpers)); } public async Task<(TestExecutingResult Result, string ResultMessage)> TestMacCatalystApp( AppBundleInformation appInformation, TimeSpan timeout, TimeSpan testLaunchTimeout, bool signalAppEnd, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, XmlResultJargon xmlResultJargon = XmlResultJargon.xUnit, string[]? skippedMethods = null, string[]? skippedTestClasses = null, CancellationToken cancellationToken = default) { var testLog = _logs.Create($"test-{TestTarget.MacCatalyst.AsString()}-{_helpers.Timestamp}.log", LogType.TestLog.ToString(), timestamp: false); var appOutputLog = _logs.Create(appInformation.BundleIdentifier + ".log", LogType.ApplicationLog.ToString(), timestamp: true); ListenerConnected = false; var (deviceListenerTransport, deviceListener, deviceListenerTmpFile) = _listenerFactory.Create( RunMode.MacOS, log: _mainLog, testLog: testLog, isSimulator: true, autoExit: true, xmlOutput: true); string? appEndTag = null; if (signalAppEnd) { WatchForAppEndTag(out appEndTag, ref appOutputLog, ref cancellationToken); } using (testLog) using (deviceListener) { var (catalystTestResult, catalystResultMessage) = await RunMacCatalystTests( deviceListenerTransport, deviceListener, deviceListenerTmpFile, appInformation, appOutputLog, timeout, testLaunchTimeout, xmlResultJargon, skippedMethods, skippedTestClasses, extraAppArguments, extraEnvVariables, appEndTag, cancellationToken); return (catalystTestResult, catalystResultMessage); } } public async Task<(TestExecutingResult Result, string ResultMessage)> TestApp( AppBundleInformation appInformation, TestTargetOs target, IDevice device, IDevice? companionDevice, TimeSpan timeout, TimeSpan testLaunchTimeout, bool signalAppEnd, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, XmlResultJargon xmlResultJargon = XmlResultJargon.xUnit, string[]? skippedMethods = null, string[]? skippedTestClasses = null, CancellationToken cancellationToken = default) { var runMode = target.Platform.ToRunMode(); var isSimulator = target.Platform.IsSimulator(); var testLog = _logs.Create($"test-{target.AsString()}-{_helpers.Timestamp}.log", LogType.TestLog.ToString(), timestamp: false); ListenerConnected = false; var (deviceListenerTransport, deviceListener, deviceListenerTmpFile) = _listenerFactory.Create( runMode, log: _mainLog, testLog: testLog, isSimulator: isSimulator, autoExit: true, xmlOutput: true); // cli always uses xml using (testLog) using (deviceListener) { var deviceListenerPort = deviceListener.InitializeAndGetPort(); deviceListener.StartAsync(); using var crashLogs = new Logs(_logs.Directory); ICrashSnapshotReporter crashReporter = _snapshotReporterFactory.Create(_mainLog, crashLogs, isDevice: !isSimulator, device.Name); using ITestReporter testReporter = _testReporterFactory.Create( _mainLog, _mainLog, _logs, crashReporter, deviceListener, _resultParser, appInformation, runMode, xmlResultJargon, device.Name, timeout, null, (level, message) => _mainLog.WriteLine(message)); IResultFileHandler resultFileHandler = new ResultFileHandler(_processManager, _mainLog); // For iOS 18+ devices/simulators, result files are copied directly from the app container // rather than transmitted over TCP. In these cases, we skip the TCP launch timeout logic // to prevent test termination when no TCP connection is established. if (deviceListener.TestLog != null && !resultFileHandler.IsVersionSupported(device.OSVersion, isSimulator)) { deviceListener.ConnectedTask .TimeoutAfter(testLaunchTimeout) .ContinueWith(async (Task task) => { testReporter.LaunchCallback(task); // Stop listening so that TCP doesn't get connected before here and when we evaluate why we failed // If no TCP happens, app didn't start in time => APP_LAUNCH_TIMEOUT // If TCP connects during this method or right after - a very narrow race condition - we would categorize it as TIMED_OUT // because we would consider the app run started and actually timing out. if (!deviceListener.ConnectedTask.IsCompleted) { await deviceListener.StopAsync(); } else if (task.IsCompleted && task.Result) { ListenerConnected = true; } }, cancellationToken) .DoNotAwait(); } _mainLog.WriteLine($"*** Executing '{appInformation.AppName}' on {target.AsString()} '{device.Name}' ***"); using var combinedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource( testReporter.CancellationToken, cancellationToken); cancellationToken = combinedCancellationToken.Token; var (enableCoverage, coverageFileName) = GetCoverageSettings(extraEnvVariables); if (isSimulator) { var mlaunchArguments = GetSimulatorArguments( appInformation, device, xmlResultJargon, skippedMethods, skippedTestClasses, deviceListenerTransport, deviceListenerPort, deviceListenerTmpFile, extraAppArguments, extraEnvVariables); await RunSimulatorTests( appInformation, mlaunchArguments, crashReporter, testReporter, resultFileHandler, deviceListener, (ISimulatorDevice)device, companionDevice as ISimulatorDevice, timeout, cancellationToken, runMode, enableCoverage, coverageFileName); } else { var appOutputLog = _logs.Create(appInformation.BundleIdentifier + ".log", LogType.ApplicationLog.ToString(), timestamp: true); string? appEndTag = null; if (signalAppEnd) { WatchForAppEndTag(out appEndTag, ref appOutputLog, ref cancellationToken); } using (appOutputLog) { var mlaunchArguments = GetDeviceArguments( appInformation, device, target.Platform.IsWatchOSTarget(), xmlResultJargon, skippedMethods, skippedTestClasses, deviceListenerTransport, deviceListenerPort, deviceListenerTmpFile, extraAppArguments, extraEnvVariables, appEndTag); await RunDeviceTests( appInformation, mlaunchArguments, crashReporter, testReporter, resultFileHandler, deviceListener, device, appOutputLog, timeout, extraEnvVariables, cancellationToken, runMode, enableCoverage, coverageFileName); } } // Check the final status, copy all the required data return await testReporter.ParseResult(); } } private async Task RunSimulatorTests( AppBundleInformation appInformation, MlaunchArguments mlaunchArguments, ICrashSnapshotReporter crashReporter, ITestReporter testReporter, IResultFileHandler resultFileHandler, ISimpleListener deviceListener, ISimulatorDevice simulator, ISimulatorDevice? companionSimulator, TimeSpan timeout, CancellationToken cancellationToken, RunMode runMode, bool enableCoverage, string coverageFileName) { var result = await RunSimulatorApp( appInformation, mlaunchArguments, crashReporter, simulator, companionSimulator, timeout, waitForExit: true, cancellationToken); await testReporter.CollectSimulatorResult(result); // On iOS 18 and later, transferring results over a TCP tunnel isn’t supported. // Instead, copy the results file from the device to the host machine. if (deviceListener.TestLog != null && resultFileHandler.IsVersionSupported(simulator.OSVersion, true)) { bool resultsCopied = await resultFileHandler.CopyResultsAsync( runMode, true, simulator.OSVersion, simulator.UDID, appInformation.BundleIdentifier, deviceListener.TestLog.FullPath); // If results weren't copied, it likely means the app crashed before tests could run // Try to retrieve the crash report, but only if the test run didn't already complete. // When test run completed (Success=true from CollectSimulatorResult), the failure is a // communication issue, not an app crash, so we skip crash report retrieval. if (!resultsCopied) { if (testReporter.Success == true) { _mainLog.WriteLine("Test results file not found after retries, but test run completed successfully. Device communication issue likely caused the copy failure."); } else { _mainLog.WriteLine("Test results file not found, app may have crashed before tests started."); await resultFileHandler.CopyCrashReportAsync( simulator.UDID, simulator.Name, appInformation, _mainLog, isSimulator: true); } } // Try to copy coverage results if coverage was enabled (non-fatal if not present) if (enableCoverage) { var coverageDest = Path.Combine(_logs.Directory, coverageFileName); if (await resultFileHandler.CopyCoverageResultsAsync(runMode, true, simulator.OSVersion, simulator.UDID, appInformation.BundleIdentifier, coverageFileName, coverageDest)) { _logs.AddFile(coverageDest, "Coverage"); _mainLog.WriteLine($"Coverage results copied to {coverageDest}"); } } } } private async Task RunDeviceTests( AppBundleInformation appInformation, MlaunchArguments mlaunchArguments, ICrashSnapshotReporter crashReporter, ITestReporter testReporter, IResultFileHandler resultFileHandler, ISimpleListener deviceListener, IDevice device, ILog appOutputLog, TimeSpan timeout, IEnumerable<(string, string?)> extraEnvVariables, CancellationToken cancellationToken, RunMode runMode, bool enableCoverage, string coverageFileName) { var deviceSystemLog = _logs.Create($"device-{device.Name}-{_helpers.Timestamp}.log", LogType.SystemLog.ToString()); deviceSystemLog.Timestamp = false; var deviceLogCapturer = _deviceLogCapturerFactory.Create(_mainLog, deviceSystemLog, device.UDID); deviceLogCapturer.StartCapture(); try { await crashReporter.StartCaptureAsync(); // create a tunnel to communicate with the device if (_listenerFactory.UseTunnel && deviceListener is SimpleTcpListener tcpListener) { // create a new tunnel using the listener var tunnel = _listenerFactory.TunnelBore.Create(device.UDID, _mainLog); tunnel.Open(device.UDID, tcpListener, timeout, _mainLog); // wait until we started the tunnel await tunnel.Started; } _mainLog.WriteLine("Starting the application"); var envVars = new Dictionary(); AddExtraEnvVars(envVars, extraEnvVariables); // We need to check for MT1111 (which means that mlaunch won't wait for the app to exit) IFileBackedLog aggregatedLog = Log.CreateReadableAggregatedLog(_mainLog, testReporter.CallbackLog); var result = await RunAndWatchForAppSignal(() => _processManager.ExecuteCommandAsync( mlaunchArguments, aggregatedLog, appOutputLog, appOutputLog, timeout, envVars, cancellationToken: cancellationToken)); await testReporter.CollectDeviceResult(result); } finally { deviceLogCapturer.StopCapture(); deviceSystemLog.Dispose(); // close a tunnel if it was created if (_listenerFactory.UseTunnel) { await _listenerFactory.TunnelBore.Close(device.UDID); } } // Upload the system log if (File.Exists(deviceSystemLog.FullPath)) { _mainLog.WriteLine("Device log captured in {0}", deviceSystemLog.FullPath); } // On iOS 18 and later, transferring results over a TCP tunnel isn’t supported. // Instead, copy the results file from the device to the host machine. if (deviceListener.TestLog != null && resultFileHandler.IsVersionSupported(device.OSVersion, false)) { bool resultsCopied = await resultFileHandler.CopyResultsAsync( runMode, false, device.OSVersion, device.UDID, appInformation.BundleIdentifier, deviceListener.TestLog.FullPath); // If results weren't copied, it likely means the app crashed before tests could run // Try to retrieve the crash report, but only if the test run didn't already complete. // When test run completed (Success=true from CollectDeviceResult), the failure is a // device communication issue, not an app crash, so we skip crash report retrieval. if (!resultsCopied) { if (testReporter.Success == true) { _mainLog.WriteLine("Test results file not found after retries, but test run completed successfully. Device communication issue likely caused the copy failure."); } else { _mainLog.WriteLine("Test results file not found, app may have crashed before tests started."); await resultFileHandler.CopyCrashReportAsync( device.UDID, device.Name, appInformation, _mainLog, isSimulator: false); } } // Try to copy coverage results if coverage was enabled (non-fatal if not present) if (enableCoverage) { var coverageDest = Path.Combine(_logs.Directory, coverageFileName); if (await resultFileHandler.CopyCoverageResultsAsync(runMode, false, device.OSVersion, device.UDID, appInformation.BundleIdentifier, coverageFileName, coverageDest)) { _logs.AddFile(coverageDest, "Coverage"); _mainLog.WriteLine($"Coverage results copied to {coverageDest}"); } } } } /// /// Runs the MacCatalyst app by executing its binary (or if not found, via `open -W path.to.app`). /// private async Task<(TestExecutingResult Result, string ResultMessage)> RunMacCatalystTests( ListenerTransport deviceListenerTransport, ISimpleListener deviceListener, string deviceListenerTmpFile, AppBundleInformation appInformation, ILog appOutputLog, TimeSpan timeout, TimeSpan testLaunchTimeout, XmlResultJargon xmlResultJargon, string[]? skippedMethods, string[]? skippedTestClasses, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, string? appEndTag, CancellationToken cancellationToken) { var deviceListenerPort = deviceListener.InitializeAndGetPort(); deviceListener.StartAsync(); var (enableCoverage, coverageFileName) = GetCoverageSettings(extraEnvVariables); using var crashLogs = new Logs(_logs.Directory); ICrashSnapshotReporter crashReporter = _snapshotReporterFactory.Create(_mainLog, crashLogs, isDevice: false, null); IResultFileHandler resultFileHandler = new ResultFileHandler(_processManager, _mainLog); using ITestReporter testReporter = _testReporterFactory.Create( _mainLog, _mainLog, _logs, crashReporter, deviceListener, _resultParser, appInformation, RunMode.MacOS, xmlResultJargon, null, timeout, null, (level, message) => _mainLog.WriteLine(message)); deviceListener.ConnectedTask .TimeoutAfter(testLaunchTimeout) .ContinueWith(async (Task task) => { testReporter.LaunchCallback(task); // Stop listening so that TCP doesn't get connected before here and when we evaluate why we failed // If no TCP happens, app didn't start in time => APP_LAUNCH_TIMEOUT // If TCP connects during this method or right after - a very narrow race condition - we would categorize it as TIMED_OUT // because we would consider the app run started and actually timing out. if (!deviceListener.ConnectedTask.IsCompleted) { await deviceListener.StopAsync(); } else if (task.IsCompleted && task.Result) { ListenerConnected = true; } }, cancellationToken) .DoNotAwait(); _mainLog.WriteLine($"*** Executing '{appInformation.AppName}' on MacCatalyst ***"); try { using var combinedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(testReporter.CancellationToken, cancellationToken); var envVariables = GetEnvVariables( xmlResultJargon, skippedMethods, skippedTestClasses, deviceListenerTransport, deviceListenerPort, deviceListenerTmpFile, extraEnvVariables, appEndTag); envVariables[EnviromentVariables.HostName] = "127.0.0.1"; AddExtraEnvVars(envVariables, extraEnvVariables); await crashReporter.StartCaptureAsync(); var result = await RunMacCatalystApp(appInformation, appOutputLog, timeout, waitForExit: true, extraAppArguments, envVariables, combinedCancellationToken.Token); await testReporter.CollectSimulatorResult(result); if (enableCoverage) { var coverageDest = Path.Combine(_logs.Directory, coverageFileName); if (await resultFileHandler.CopyCoverageResultsAsync( RunMode.MacOS, isSimulator: false, osVersion: Environment.OSVersion.Version.ToString(), udid: string.Empty, appInformation.BundleIdentifier, coverageFileName, coverageDest)) { _logs.AddFile(coverageDest, "Coverage"); _mainLog.WriteLine($"Coverage results copied to {coverageDest}"); } } } finally { deviceListener.Cancel(); } return await testReporter.ParseResult(); } private static (bool EnableCoverage, string CoverageFileName) GetCoverageSettings(IEnumerable<(string, string?)> extraEnvVariables) { bool enableCoverage = extraEnvVariables.Any(e => e.Item1 == "NUNIT_ENABLE_COVERAGE"); string coverageFileName = extraEnvVariables .Where(e => e.Item1 == "NUNIT_COVERAGE_OUTPUT_PATH") .Select(e => e.Item2) .FirstOrDefault() ?? "coverage.cobertura.xml"; return (enableCoverage, coverageFileName); } private Dictionary GetEnvVariables( XmlResultJargon xmlResultJargon, string[]? skippedMethods, string[]? skippedTestClasses, ListenerTransport listenerTransport, int listenerPort, string listenerTmpFile, IEnumerable<(string, string?)> extraEnvVariables, string? appEndTag) { var variables = new Dictionary { { EnviromentVariables.AutoExit, "true" }, { EnviromentVariables.HostPort, listenerPort.ToString() }, // Let the runner know that we want to get an XML output and not plain text { EnviromentVariables.EnableXmlOutput, "true" }, { EnviromentVariables.XmlVersion, xmlResultJargon.ToString() }, }; if (skippedMethods?.Any() ?? skippedTestClasses?.Any() ?? false) { // Do not run all the tests, we are using filters variables.Add(EnviromentVariables.RunAllTestsByDefault, "false"); // Add the skipped test classes and methods if (skippedMethods != null && skippedMethods.Length > 0) { var skippedMethodsValue = string.Join(',', skippedMethods); variables.Add(EnviromentVariables.SkippedMethods, skippedMethodsValue); } if (skippedTestClasses != null && skippedTestClasses!.Length > 0) { var skippedClassesValue = string.Join(',', skippedTestClasses); variables.Add(EnviromentVariables.SkippedClasses, skippedClassesValue); } } if (listenerTransport == ListenerTransport.File) { variables.Add(EnviromentVariables.LogFilePath, listenerTmpFile); } if (appEndTag != null) { variables.Add(EnviromentVariables.AppEndTag, appEndTag); } AddExtraEnvVars(variables, extraEnvVariables); return variables; } private MlaunchArguments GetCommonArguments( XmlResultJargon xmlResultJargon, string[]? skippedMethods, string[]? skippedTestClasses, ListenerTransport listenerTransport, int listenerPort, string listenerTmpFile, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, string? appEndTag) { var args = new MlaunchArguments(); // Environment variables var envVariables = GetEnvVariables( xmlResultJargon, skippedMethods, skippedTestClasses, listenerTransport, listenerPort, listenerTmpFile, extraEnvVariables, appEndTag); // Variables passed through --set-env args.AddRange(GetSetEnvVariableArguments(envVariables)); // Arguments passed to the iOS app bundle args.AddRange(extraAppArguments.Select(arg => new SetAppArgumentArgument(arg))); return args; } private MlaunchArguments GetSimulatorArguments( AppBundleInformation appInformation, IDevice simulator, XmlResultJargon xmlResultJargon, string[]? skippedMethods, string[]? skippedTestClasses, ListenerTransport deviceListenerTransport, int deviceListenerPort, string deviceListenerTmpFile, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables) { var args = GetCommonArguments( xmlResultJargon, skippedMethods, skippedTestClasses, deviceListenerTransport, deviceListenerPort, deviceListenerTmpFile, extraAppArguments, extraEnvVariables, appEndTag: null); args.Add(new SetEnvVariableArgument(EnviromentVariables.HostName, "127.0.0.1")); args.Add(new SimulatorUDIDArgument(simulator)); if (appInformation.Extension.HasValue) { switch (appInformation.Extension) { case Extension.TodayExtension: args.Add(new LaunchSimulatorExtensionArgument(appInformation.LaunchAppPath, appInformation.BundleIdentifier)); break; case Extension.WatchKit2: default: throw new NotImplementedException(); } } else { args.Add(new LaunchSimulatorBundleArgument(appInformation)); } return args; } private MlaunchArguments GetDeviceArguments( AppBundleInformation appInformation, IDevice device, bool isWatchTarget, XmlResultJargon xmlResultJargon, string[]? skippedMethods, string[]? skippedTestClasses, ListenerTransport deviceListenerTransport, int deviceListenerPort, string deviceListenerTmpFile, IEnumerable extraAppArguments, IEnumerable<(string, string?)> extraEnvVariables, string? appEndTag) { var args = GetCommonArguments( xmlResultJargon, skippedMethods, skippedTestClasses, deviceListenerTransport, deviceListenerPort, deviceListenerTmpFile, extraAppArguments, extraEnvVariables, appEndTag); var ips = string.Join(",", _helpers.GetLocalIpAddresses().Select(ip => ip.ToString())); args.Add(new SetEnvVariableArgument(EnviromentVariables.HostName, ips)); args.Add(new DisableMemoryLimitsArgument()); args.Add(new DeviceNameArgument(device)); if (_listenerFactory.UseTunnel) { args.Add(new SetEnvVariableArgument(EnviromentVariables.UseTcpTunnel, true)); } if (appInformation.Extension.HasValue) { switch (appInformation.Extension) { case Extension.TodayExtension: args.Add(new LaunchDeviceExtensionArgument(appInformation.LaunchAppPath, appInformation.BundleIdentifier)); break; case Extension.WatchKit2: default: throw new NotImplementedException(); } } else { args.Add(new LaunchDeviceBundleIdArgument(appInformation)); } if (isWatchTarget) { args.Add(new AttachNativeDebuggerArgument()); // this prevents the watch from backgrounding the app. } else { args.Add(new WaitForExitArgument()); } return args; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/AppOperations/AppTesterFactory.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Listeners; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface IAppTesterFactory { IAppTester Create(CommunicationChannel communicationChannel, bool isSimulator, IFileBackedLog log, ILogs logs, Action? logCallback, Version? osVersion); } public class AppTesterFactory : IAppTesterFactory { private readonly IMlaunchProcessManager _processManager; private readonly ICrashSnapshotReporterFactory _snapshotReporterFactory; private readonly ICaptureLogFactory _captureLogFactory; private readonly IDeviceLogCapturerFactory _deviceLogCapturerFactory; private readonly ITestReporterFactory _reporterFactory; private readonly IResultParser _resultParser; private readonly IHelpers _helpers; public AppTesterFactory( IMlaunchProcessManager processManager, ICrashSnapshotReporterFactory snapshotReporterFactory, ICaptureLogFactory captureLogFactory, IDeviceLogCapturerFactory deviceLogCapturerFactory, ITestReporterFactory reporterFactory, IResultParser resultParser, IHelpers helpers) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _snapshotReporterFactory = snapshotReporterFactory ?? throw new ArgumentNullException(nameof(snapshotReporterFactory)); _captureLogFactory = captureLogFactory ?? throw new ArgumentNullException(nameof(captureLogFactory)); _deviceLogCapturerFactory = deviceLogCapturerFactory ?? throw new ArgumentNullException(nameof(deviceLogCapturerFactory)); _reporterFactory = reporterFactory ?? throw new ArgumentNullException(nameof(reporterFactory)); _resultParser = resultParser ?? throw new ArgumentNullException(nameof(resultParser)); _helpers = helpers ?? throw new ArgumentNullException(nameof(helpers)); } public IAppTester Create( CommunicationChannel communicationChannel, bool isSimulator, IFileBackedLog log, ILogs logs, Action? logCallback, Version? osVersion) { // On iOS 18 and later, transferring results over a TCP tunnel isn’t supported. var tunnelBore = (communicationChannel == CommunicationChannel.UsbTunnel && !isSimulator && osVersion !=null && osVersion.Major < 18) ? new TunnelBore(_processManager) : null; return new AppTester( _processManager, new SimpleListenerFactory(tunnelBore), _snapshotReporterFactory, _captureLogFactory, _deviceLogCapturerFactory, _reporterFactory, _resultParser, log, logs, _helpers, logCallback); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/AppOperations/AppUninstaller.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; namespace Microsoft.DotNet.XHarness.Apple; public interface IAppUninstaller { Task UninstallSimulatorApp(ISimulatorDevice simulator, string appBundleId, CancellationToken cancellationToken = default); Task UninstallDeviceApp(IHardwareDevice device, string appBundleId, CancellationToken cancellationToken = default); } public class AppUninstaller : IAppUninstaller { private readonly IMlaunchProcessManager _processManager; private readonly ILog _mainLog; public AppUninstaller(IMlaunchProcessManager processManager, ILog mainLog) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _mainLog = mainLog ?? throw new ArgumentNullException(nameof(mainLog)); } public Task UninstallSimulatorApp(ISimulatorDevice simulator, string appBundleId, CancellationToken cancellationToken = default) => _processManager.ExecuteXcodeCommandAsync( "simctl", new[] { "uninstall", simulator.UDID, appBundleId }, _mainLog, TimeSpan.FromMinutes(3), cancellationToken: cancellationToken); public Task UninstallDeviceApp(IHardwareDevice device, string appBundleId, CancellationToken cancellationToken = default) { var args = new MlaunchArguments { new UninstallAppFromDeviceArgument(appBundleId), new DeviceNameArgument(device) }; return _processManager.ExecuteCommandAsync(args, _mainLog, TimeSpan.FromMinutes(3), cancellationToken: cancellationToken); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/CommunicationChannel.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.Apple; /// /// Specifies the channel that is used to communicate with the device. /// public enum CommunicationChannel { /// /// Connect to the device using the LAN or WAN. /// Network, /// /// Connect to the device using a tcp-tunnel /// UsbTunnel, } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/CrashSnapshotReporterFactory.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; namespace Microsoft.DotNet.XHarness.Apple; public interface ICrashSnapshotReporterFactory { ICrashSnapshotReporter Create(ILog log, ILogs logs, bool isDevice, string? deviceName); } public class CrashSnapshotReporterFactory : ICrashSnapshotReporterFactory { private readonly IMlaunchProcessManager _processManager; public CrashSnapshotReporterFactory(IMlaunchProcessManager processManager) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); } public ICrashSnapshotReporter Create(ILog log, ILogs logs, bool isDevice, string? deviceName) => new CrashSnapshotReporter(_processManager, log, logs, isDevice, deviceName); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/Darwin.cs ================================================ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Runtime.InteropServices; namespace Microsoft.DotNet.XHarness.Apple; public static class Darwin { private const int CTL_KERN = 1; private const int KERN_OSRELEASE = 2; private static string GetKernelRelease() { unsafe { const uint BUFFER_LENGTH = 32; var name = stackalloc int[2]; name[0] = CTL_KERN; name[1] = KERN_OSRELEASE; var buf = stackalloc byte[(int)BUFFER_LENGTH]; var len = stackalloc uint[1]; *len = BUFFER_LENGTH; try { // If the buffer isn't big enough, it seems sysctl still returns 0 and just sets len to the // necessary buffer size. This appears to be contrary to the man page, but it's easy to detect // by simply checking len against the buffer length. if (sysctl(name, 2, buf, len, IntPtr.Zero, 0) == 0 && *len < BUFFER_LENGTH) { return Marshal.PtrToStringAnsi((IntPtr)buf, (int)*len); } } catch (Exception ex) { throw new PlatformNotSupportedException("Error reading Darwin Kernel Version", ex); } throw new PlatformNotSupportedException("Unknown error reading Darwin Kernel Version"); } } public static string GetVersion() { var kernelRelease = GetKernelRelease(); if (!Version.TryParse(kernelRelease, out Version? version) || version.Major < 5) { // 10.0 covers all versions prior to Darwin 5 // Similarly, if the version is not a valid version number, but we have still detected that it is Darwin, we just assume // it is OS X 10.0 return "10.0"; } else { // Mac OS X 10.1 mapped to Darwin 5.x, and the mapping continues that way // So just subtract 4 from the Darwin version. // https://en.wikipedia.org/wiki/Darwin_%28operating_system%29 return $"10.{version.Major - 4}"; } } [DllImport("libc")] private static extern unsafe int sysctl( int* name, uint namelen, byte* oldp, uint* oldlenp, IntPtr newp, uint newlen); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/DeviceFinder.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface IDeviceFinder { Task FindDevice( TestTargetOs target, string? deviceName, ILog log, bool includeWirelessDevices = true, bool pairedDevicesOnly = true, CancellationToken cancellationToken = default); } public record DevicePair(IDevice Device, IDevice? CompanionDevice); public class DeviceFinder : IDeviceFinder { private readonly IHardwareDeviceLoader _deviceLoader; private readonly ISimulatorLoader _simulatorLoader; public DeviceFinder(IHardwareDeviceLoader deviceLoader, ISimulatorLoader simulatorLoader) { _deviceLoader = deviceLoader ?? throw new ArgumentNullException(nameof(deviceLoader)); _simulatorLoader = simulatorLoader ?? throw new ArgumentNullException(nameof(simulatorLoader)); } public async Task FindDevice( TestTargetOs target, string? deviceName, ILog log, bool includeWirelessDevices = true, bool pairedDevicesOnly = true, CancellationToken cancellationToken = default) { IDevice? device; IDevice? companionDevice = null; bool IsMatchingDevice(IDevice device) => device.Name.Equals(deviceName, StringComparison.InvariantCultureIgnoreCase) || device.UDID.Equals(deviceName, StringComparison.InvariantCultureIgnoreCase); if (target.Platform.IsSimulator()) { if (deviceName == null) { (device, companionDevice) = await _simulatorLoader.FindSimulators(target, log, retryCount: 3, cancellationToken: cancellationToken); } else { await _simulatorLoader.LoadDevices(log, includeLocked: false, cancellationToken: cancellationToken); device = _simulatorLoader.AvailableDevices.FirstOrDefault(IsMatchingDevice) ?? throw new NoDeviceFoundException($"Failed to find a simulator '{deviceName}'"); } } else { // The DeviceLoader.FindDevice will return the fist device of the type, but we want to make sure that // the device we use is of the correct arch, therefore, we will use the LoadDevices and handpick one await _deviceLoader.LoadDevices( log, includeLocked: false, forceRefresh: false, includeWirelessDevices: includeWirelessDevices, cancellationToken: cancellationToken); if (deviceName == null) { IHardwareDevice? hardwareDevice = target.Platform switch { TestTarget.Device_iOS => _deviceLoader.Connected64BitIOS.FirstOrDefault(d => !pairedDevicesOnly || d.IsPaired), TestTarget.Device_tvOS => _deviceLoader.ConnectedTV.FirstOrDefault(d => !pairedDevicesOnly || d.IsPaired), _ => throw new ArgumentOutOfRangeException(nameof(target), $"Unrecognized device platform {target.Platform}") }; if (target.Platform.IsWatchOSTarget() && hardwareDevice != null) { companionDevice = await _deviceLoader.FindCompanionDevice(log, hardwareDevice, cancellationToken: cancellationToken); } device = hardwareDevice; } else { device = _deviceLoader.ConnectedDevices.FirstOrDefault(IsMatchingDevice) ?? throw new NoDeviceFoundException($"Failed to find a device '{deviceName}'. " + "Please make sure the device is connected and unlocked."); } } if (device == null) { throw new NoDeviceFoundException($"Failed to find a suitable device for target {target.AsString()}"); } return new DevicePair(device, companionDevice); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/DeviceLogCapturerFactory.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; namespace Microsoft.DotNet.XHarness.Apple; public interface IDeviceLogCapturerFactory { IDeviceLogCapturer Create(ILog mainLog, ILog deviceLog, string deviceUdid); } public class DeviceLogCapturerFactory : IDeviceLogCapturerFactory { public IDeviceLogCapturer Create(ILog mainLog, ILog deviceLog, string deviceUdid) => new DeviceLogCapturer(mainLog, deviceLog, deviceUdid); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/ErrorKnowledgeBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; namespace Microsoft.DotNet.XHarness.Apple; public class ErrorKnowledgeBase : IErrorKnowledgeBase { private static readonly Dictionary s_testErrorMaps = new() { ["Failed to communicate with the device"] = new("Failed to communicate with the device. Please ensure the cable is properly connected, and try rebooting the device", suggestedExitCode: (int)ExitCode.DEVICE_FAILURE), ["MT1031"] = new("Cannot launch the application because the device is locked. Please unlock the device and try again", suggestedExitCode: (int)ExitCode.DEVICE_FAILURE), ["the device is locked"] = new("Cannot launch the application because the device is locked. Please unlock the device and try again", suggestedExitCode: (int)ExitCode.DEVICE_FAILURE), ["while Setup Assistant is running"] = new("Cannot launch the application because the device's update hasn't been finished. The setup assistant is still running. Please finish the device OS update on the device", suggestedExitCode: (int)ExitCode.DEVICE_FAILURE), ["LSOpenURLsWithRole() failed with error -10825"] = new("This application requires a newer version of MacOS", suggestedExitCode: (int)ExitCode.GENERAL_FAILURE), ["Failed to start launchd_sim: could not bind to session"] = new("Failed to launch the Simulator as XHarness was most likely started from a user session without GUI capabilities (e.g. from a launchd daemon). " + "Please start XHarness from a full user session or bind the run to one via `sudo launchctl asuser`", suggestedExitCode: (int)ExitCode.APP_LAUNCH_FAILURE), ["error HE0018: Could not launch the simulator application"] = new("Failed to launch the Simulator, please try again. If the problem persists, try rebooting MacOS", suggestedExitCode: (int)ExitCode.SIMULATOR_FAILURE), ["error HE0042: Could not launch the app"] = new("Failed to launch the application, please try again. If the problem persists, try rebooting MacOS", suggestedExitCode: (int)ExitCode.APP_LAUNCH_FAILURE), ["[TCP tunnel] Xamarin.Hosting: Failed to connect to port"] = new( "TCP Tunnel Connection Failed to connect to TCP port", suggestedExitCode: (int)ExitCode.TCP_CONNECTION_FAILED), }; private static readonly Dictionary s_buildErrorMaps = new(); private static readonly Dictionary s_installErrorMaps = new() { ["IncorrectArchitecture"] = new("IncorrectArchitecture: Failed to find matching device arch for the application"), // known failure, but not an issue ["0xe8008015"] = new("No valid provisioning profile found", suggestedExitCode: (int)ExitCode.APP_NOT_SIGNED), ["valid provisioning profile for this executable was not found"] = new("No valid provisioning profile found", suggestedExitCode: (int)ExitCode.APP_NOT_SIGNED), ["0xe800801c"] = new("App is not signed", suggestedExitCode: (int)ExitCode.APP_NOT_SIGNED), ["No code signature found"] = new("App is not signed", suggestedExitCode: (int)ExitCode.APP_NOT_SIGNED), }; public bool IsKnownBuildIssue(IFileBackedLog buildLog, [NotNullWhen(true)] out KnownIssue? knownFailureMessage) => TryFindErrors(buildLog, s_buildErrorMaps, out knownFailureMessage); public bool IsKnownTestIssue(IFileBackedLog runLog, [NotNullWhen(true)] out KnownIssue? knownFailureMessage) => TryFindErrors(runLog, s_testErrorMaps, out knownFailureMessage); public bool IsKnownInstallIssue(IFileBackedLog installLog, [NotNullWhen(true)] out KnownIssue? knownFailureMessage) => TryFindErrors(installLog, s_installErrorMaps, out knownFailureMessage); private static bool TryFindErrors(IFileBackedLog log, Dictionary errorMap, [NotNullWhen(true)] out KnownIssue? failureMessage) { failureMessage = null; if (log == null) { return false; } if (!File.Exists(log.FullPath) || new FileInfo(log.FullPath).Length <= 0) { return false; } using var reader = log.GetReader(); while (!reader.EndOfStream) { var line = reader.ReadLine(); if (line == null) { continue; } //go over errors and return true as soon as we find one that matches foreach (var error in errorMap.Keys) { if (!line.Contains(error, StringComparison.InvariantCultureIgnoreCase)) { continue; } failureMessage = errorMap[error]; return true; } } return false; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/ExitCodeDetector.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Text.RegularExpressions; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; namespace Microsoft.DotNet.XHarness.Apple; public interface IExitCodeDetector { int? DetectExitCode(AppBundleInformation appBundleInfo, IReadableLog systemLog); } public interface IiOSExitCodeDetector : IExitCodeDetector { } public interface IMacCatalystExitCodeDetector : IExitCodeDetector { } public abstract class ExitCodeDetector : IExitCodeDetector { // This tag is logged by the dotnet/runtime Apple app wrapper // https://github.com/dotnet/runtime/blob/a883caa0803778084167b978281c34db8e753246/src/tasks/AppleAppBuilder/Templates/runtime.m#L30 protected const string DotnetAppExitTag = "DOTNET.APP_EXIT_CODE:"; // This line is logged by MacOS protected const string AbnormalExitMessage = "Service exited with abnormal code"; public int? DetectExitCode(AppBundleInformation appBundleInfo, IReadableLog log) { StreamReader reader; try { reader = log.GetReader(); } catch (FileNotFoundException e) { throw new Exception("Failed to detect application's exit code. The log file was empty / not found at " + e.FileName, e); } using (reader) while (!reader.EndOfStream) { if (reader.ReadLine() is string line && IsSignalLine(appBundleInfo, line) is Match match && match.Success && int.TryParse(match.Groups["exitCode"].Value, out var exitCode)) { return exitCode; } } return null; } protected virtual Match? IsSignalLine(AppBundleInformation appBundleInfo, string logLine) { if (IsAbnormalExitLine(appBundleInfo, logLine) || IsStdoutExitLine(appBundleInfo, logLine)) { return EoLExitCodeRegex.Match(logLine); } return null; } protected Regex EoLExitCodeRegex { get; } = new Regex(@" (?-?[0-9]+)$", RegexOptions.Compiled); // Example line coming from app's stdout log stream // 2022-03-18 12:48:53.336 I Microsoft.Extensions.Configuration.CommandLine.Tests[12477:10069] DOTNET.APP_EXIT_CODE: 0 private static bool IsStdoutExitLine(AppBundleInformation appBundleInfo, string logLine) => logLine.Contains(DotnetAppExitTag) && logLine.Contains(appBundleInfo.BundleExecutable ?? appBundleInfo.BundleIdentifier); // Example line // Feb 18 06:40:16 Admins-Mac-Mini com.apple.xpc.launchd[1] (net.dot.System.Buffers.Tests.15140[59229]): Service exited with abnormal code: 74 private static bool IsAbnormalExitLine(AppBundleInformation appBundleInfo, string logLine) => logLine.Contains(AbnormalExitMessage) && (logLine.Contains(appBundleInfo.AppName) || logLine.Contains(appBundleInfo.BundleIdentifier)); } public class iOSExitCodeDetector : ExitCodeDetector, IiOSExitCodeDetector { // Example line coming from the mlaunch log // [07:02:21.6637600] Application 'net.dot.iOS.Simulator.PInvoke.Test' terminated (with exit code '42' and/or crashing signal '). private Regex[] DeviceExitCodeRegexes { get; } = new Regex[] { new Regex(@"terminated \(with exit code '(?-?[0-9]+)' and/or crashing signal", RegexOptions.Compiled), new Regex(@"Failed to execute 'devicectl':.*returned the exit code (?\d+)\.", RegexOptions.Compiled), new Regex(@"Process mlaunch exited with (?\d+)\.?", RegexOptions.Compiled) }; protected override Match? IsSignalLine(AppBundleInformation appBundleInfo, string logLine) { if (base.IsSignalLine(appBundleInfo, logLine) is Match match && match.Success) { return match; } if (logLine.Contains(appBundleInfo.BundleIdentifier)) { foreach (var regex in DeviceExitCodeRegexes) { var m = regex.Match(logLine); if (m.Success) { return m; } } } return null; } } public class MacCatalystExitCodeDetector : ExitCodeDetector, IMacCatalystExitCodeDetector { } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/ILogger.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.Apple; /// /// Simple interface that can be implemented either via Microsoft.Extensions.Logging logger or /// with some other means when not invoked from command line. /// The purpose is to not have dependency on Microsoft.Extensions.Logging specifically in this project. /// public interface ILogger { void LogDebug(string message); void LogInformation(string message); void LogWarning(string message); void LogError(string message); void LogCritical(string message); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/Microsoft.DotNet.XHarness.Apple.csproj ================================================ $(XHarnessNetTFMs) true ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/Orchestration/BaseOrchestrator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; /// /// Base class that implements the high level flow that enables running iOS/tvOS/MacCatalyst apps: /// - Find device (+ prepare / reset) /// - Install app /// - Run/Test app (abstract) /// - Clean up / uninstall /// - Dispose everything properly /// public abstract class BaseOrchestrator : IDisposable { protected static readonly string s_mlaunchLldbConfigFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".mtouch-launch-with-lldb"); private readonly IAppBundleInformationParser _appBundleInformationParser; private readonly IAppInstaller _appInstaller; private readonly IAppUninstaller _appUninstaller; private readonly IDeviceFinder _deviceFinder; private readonly ILogger _logger; private readonly ILogs _logs; private readonly IFileBackedLog _mainLog; private readonly IErrorKnowledgeBase _errorKnowledgeBase; private readonly IDiagnosticsData _diagnosticsData; private readonly IHelpers _helpers; private bool _lldbFileCreated; // This is needed because // - For simulators, we query the simulator for Info.plist location and parse it // - For full commands, we have path to Info.plist directly and parse it // - For MacCatalyst or just- commands on devices, we don't even need it fully initialized public delegate Task GetAppBundleInfoFunc(TestTargetOs target, IDevice device, CancellationToken cancellationToken); // This is what different commands (run/test) use to inject the actual way how they want to run the MacCatalyst app public delegate Task ExecuteMacCatalystAppFunc(AppBundleInformation appBundleInfo); // This is what different commands (run/test) use to inject the actual way how they want to run the simulator/device app public delegate Task ExecuteAppFunc(AppBundleInformation appBundleInfo, IDevice device, IDevice? companion); protected BaseOrchestrator( IAppBundleInformationParser appBundleInformationParser, IAppInstaller appInstaller, IAppUninstaller appUninstaller, IDeviceFinder deviceFinder, ILogger consoleLogger, ILogs logs, IFileBackedLog mainLog, IErrorKnowledgeBase errorKnowledgeBase, IDiagnosticsData diagnosticsData, IHelpers helpers) { _appBundleInformationParser = appBundleInformationParser ?? throw new ArgumentNullException(nameof(appBundleInformationParser)); _appInstaller = appInstaller ?? throw new ArgumentNullException(nameof(appInstaller)); _appUninstaller = appUninstaller ?? throw new ArgumentNullException(nameof(appUninstaller)); _deviceFinder = deviceFinder ?? throw new ArgumentNullException(nameof(deviceFinder)); _logger = consoleLogger ?? throw new ArgumentNullException(nameof(consoleLogger)); _logs = logs ?? throw new ArgumentNullException(nameof(logs)); _mainLog = mainLog ?? throw new ArgumentNullException(nameof(mainLog)); _errorKnowledgeBase = errorKnowledgeBase ?? throw new ArgumentNullException(nameof(errorKnowledgeBase)); _diagnosticsData = diagnosticsData ?? throw new ArgumentNullException(nameof(diagnosticsData)); _helpers = helpers ?? throw new ArgumentNullException(nameof(helpers)); } protected async Task OrchestrateOperation( TestTargetOs target, string? deviceName, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, GetAppBundleInfoFunc getAppBundle, ExecuteMacCatalystAppFunc executeMacCatalystApp, ExecuteAppFunc executeApp, CancellationToken cancellationToken) { ExitCode exitCode = ExitCode.GENERAL_FAILURE; try { exitCode = await OrchestrateOperationInternal( target, deviceName, includeWirelessDevices, resetSimulator, enableLldb, getAppBundle, executeMacCatalystApp, executeApp, cancellationToken); } catch (OperationCanceledException e) { _logger.LogDebug(e.ToString()); exitCode = ExitCode.APP_LAUNCH_TIMEOUT; } finally { EmitAppleRunSummary(exitCode); } return exitCode; } private async Task OrchestrateOperationInternal( TestTargetOs target, string? deviceName, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, GetAppBundleInfoFunc getAppBundle, ExecuteMacCatalystAppFunc executeMacCatalystApp, ExecuteAppFunc executeApp, CancellationToken cancellationToken) { _lldbFileCreated = false; var isLldbEnabled = IsLldbEnabled(); if (isLldbEnabled && !enableLldb) { // the file is present, but the user did not set it, warn him about it _logger.LogWarning("Lldb will be used since '~/.mtouch-launch-with-lldb' was found in the system but it was not created by xharness."); } else if (enableLldb) { if (!File.Exists(s_mlaunchLldbConfigFile)) { // create empty file File.WriteAllText(s_mlaunchLldbConfigFile, string.Empty); _lldbFileCreated = true; } } if (includeWirelessDevices && target.Platform.IsSimulator()) { _logger.LogWarning("Including wireless devices while targeting a simulator has no effect"); } if (resetSimulator && !target.Platform.IsSimulator()) { _logger.LogWarning("Targeting device but requesting simulator reset has no effect"); resetSimulator = false; } ExitCode exitCode; IDevice device; IDevice? companionDevice; AppBundleInformation appBundleInfo; if (target.Platform == TestTarget.MacCatalyst) { // MacCatalyst runs on the local Mac — set device info accordingly _diagnosticsData.Device = Environment.MachineName; _diagnosticsData.TargetOS = $"macOS {Environment.OSVersion.Version}"; _diagnosticsData.IsDevice = false; try { appBundleInfo = await getAppBundle(target, null!, cancellationToken); } catch (Exception e) { cancellationToken.ThrowIfCancellationRequested(); _logger.LogError(e.Message); return ExitCode.PACKAGE_NOT_FOUND; } try { exitCode = await executeMacCatalystApp(appBundleInfo); } catch (Exception e) { var message = new StringBuilder().AppendLine("Application run failed:"); exitCode = ExitCode.APP_LAUNCH_FAILURE; if (_errorKnowledgeBase.IsKnownTestIssue(_mainLog, out var failure)) { message.Append(failure.HumanMessage); if (failure.IssueLink != null) { message.AppendLine($" Find more information at {failure.IssueLink}"); } if (failure.SuggestedExitCode.HasValue) { exitCode = (ExitCode)failure.SuggestedExitCode.Value; } } else { message.AppendLine(e.ToString()); } _logger.LogError(message.ToString()); } return exitCode; } try { _logger.LogInformation($"Looking for available {target.AsString()} {(target.Platform.IsSimulator() ? "simulators" : "devices")}.."); var finderLogName = $"list-{target.AsString()}-{_helpers.Timestamp}.log"; using var finderLog = _logs.Create(finderLogName, "DeviceList", true); _mainLog.WriteLine( $"Looking for available {target.AsString()} {(target.Platform.IsSimulator() ? "simulators" : "devices")}. " + $"Storing logs into {finderLogName}"); (device, companionDevice) = await _deviceFinder.FindDevice( target, deviceName, finderLog, includeWirelessDevices, pairedDevicesOnly: true, cancellationToken); _logger.LogInformation($"Found {(target.Platform.IsSimulator() ? "simulator" : "physical")} device '{device.Name}'"); if (companionDevice != null) { _logger.LogInformation($"Found companion {(target.Platform.IsSimulator() ? "simulator" : "physical")} device '{companionDevice.Name}'"); } } catch (NoDeviceFoundException e) { _logger.LogError(e.Message); return ExitCode.DEVICE_NOT_FOUND; } cancellationToken.ThrowIfCancellationRequested(); try { appBundleInfo = await getAppBundle(target, device, cancellationToken); } catch (Exception e) { cancellationToken.ThrowIfCancellationRequested(); _logger.LogError(e.Message); return ExitCode.PACKAGE_NOT_FOUND; } cancellationToken.ThrowIfCancellationRequested(); if (target.Platform.IsSimulator() && resetSimulator) { try { var simulator = (ISimulatorDevice)device; var bundleIds = appBundleInfo.BundleIdentifier == string.Empty ? Array.Empty() : new[] { appBundleInfo.BundleIdentifier }; _logger.LogInformation($"Reseting simulator '{device.Name}'"); await simulator.PrepareSimulator(_mainLog, bundleIds); if (companionDevice != null) { _logger.LogInformation($"Reseting companion simulator '{companionDevice.Name}'"); var companionSimulator = (ISimulatorDevice)companionDevice; await companionSimulator.PrepareSimulator(_mainLog, bundleIds); } _logger.LogInformation("Simulator reset finished"); } catch (Exception e) { _logger.LogError($"Failed to reset simulator: " + Environment.NewLine + e); return ExitCode.SIMULATOR_FAILURE; } } cancellationToken.ThrowIfCancellationRequested(); // Note down the actual test target // For simulators (e.g. "iOS 13.4"), we strip the iOS part and keep the version only, for devices there's no OS _diagnosticsData.TargetOS = target.Platform.IsSimulator() ? device.OSVersion.Split(' ', 2).Last() : device.OSVersion; _diagnosticsData.Device = device.Name ?? device.UDID; // Uninstall the app first to get a clean state if (!resetSimulator) { await UninstallApp(target.Platform, appBundleInfo.BundleIdentifier, device, isPreparation: true, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); } exitCode = await InstallApp(appBundleInfo, device, target, cancellationToken); if (exitCode != ExitCode.SUCCESS) { _logger.LogInformation($"Cleaning up the failed installation from '{device.Name}'"); var uninstallResult = await UninstallApp(target.Platform, appBundleInfo.BundleIdentifier, device, isPreparation: false, new CancellationToken()); if (uninstallResult == ExitCode.SIMULATOR_FAILURE) { // Sometimes the simulator gets in a bad shape and we won't be able to install the app, we can tell here return ExitCode.SIMULATOR_FAILURE; } return exitCode; } try { exitCode = await executeApp(appBundleInfo, device, companionDevice); } catch (Exception e) { exitCode = ExitCode.GENERAL_FAILURE; var message = new StringBuilder().AppendLine("Application run failed:"); if (_errorKnowledgeBase.IsKnownTestIssue(_mainLog, out var failure)) { message.Append(failure.HumanMessage); if (failure.IssueLink != null) { message.AppendLine($" Find more information at {failure.IssueLink}"); } if (failure.SuggestedExitCode.HasValue) { exitCode = (ExitCode)failure.SuggestedExitCode.Value; } } else { message.AppendLine(e.ToString()); } _logger.LogError(message.ToString()); } finally { if (target.Platform.IsSimulator() && resetSimulator) { await CleanUpSimulators(device, companionDevice); } else if (device != null) // Do not uninstall if device was cleaned up { var uninstallResult = await UninstallApp(target.Platform, appBundleInfo.BundleIdentifier, device, false, new CancellationToken()); // We are able to detect a case when simulator is in a bad shape // If it also failed the test/run, we should present that as the failure if (uninstallResult == ExitCode.SIMULATOR_FAILURE && exitCode != ExitCode.SUCCESS && exitCode != ExitCode.TESTS_FAILED) { exitCode = ExitCode.SIMULATOR_FAILURE; } } } return exitCode; } protected void EmitAppleRunSummary(ExitCode exitCode) { var producedFiles = new List(); // Collect files from the logs collection foreach (var log in _logs.OfType()) { if (string.IsNullOrEmpty(log.FullPath) || !File.Exists(log.FullPath)) { continue; } // Skip empty log files var fileInfo = new FileInfo(log.FullPath); if (fileInfo.Length == 0) { continue; } var fileName = Path.GetFileName(log.FullPath); var fileType = log.Description ?? "log"; producedFiles.Add(new DiagnosticsFile { Name = fileName, Type = fileType.ToLowerInvariant().Replace(" ", "-"), Path = log.FullPath, }); } // Also populate diagnostics data files foreach (var file in producedFiles) { _diagnosticsData.Files.Add(file); } RunSummaryEmitter.EmitRunSummary( message => _logger.LogInformation(message), exitCode, platform: "apple", deviceName: _diagnosticsData.Device, deviceOsVersion: _diagnosticsData.TargetOS, architecture: null, instrumentationExitCode: null, producedFiles: producedFiles); RunSummaryEmitter.WriteResultJsonFile( _logs.Directory, exitCode, platform: "apple", deviceName: _diagnosticsData.Device, deviceOsVersion: _diagnosticsData.TargetOS, architecture: null, instrumentationExitCode: null, producedFiles: producedFiles); } public void Dispose() { _mainLog.Dispose(); if (_lldbFileCreated) { File.Delete(s_mlaunchLldbConfigFile); } GC.SuppressFinalize(this); } protected virtual async Task InstallApp( AppBundleInformation appBundleInfo, IDevice device, TestTargetOs target, CancellationToken cancellationToken) { _logger.LogInformation($"Installing application '{appBundleInfo.AppName}' on '{device.Name}'"); ProcessExecutionResult result; try { result = await _appInstaller.InstallApp(appBundleInfo, target, device, cancellationToken); } catch (Exception e) { _logger.LogError($"Failed to install the app bundle:{Environment.NewLine}{e}"); return ExitCode.PACKAGE_INSTALLATION_FAILURE; } if (!result.Succeeded) { cancellationToken.ThrowIfCancellationRequested(); var exitCode = ExitCode.PACKAGE_INSTALLATION_FAILURE; // use the knowledge base class to decide if the error is known, if it is, let the user know // the failure reason if (_errorKnowledgeBase.IsKnownInstallIssue(_mainLog, out var failure)) { var error = new StringBuilder() .AppendLine("Failed to install the application") .AppendLine(failure.HumanMessage); if (failure.IssueLink != null) { error .AppendLine() .AppendLine($" Find more information at {failure.IssueLink}"); } if (failure.SuggestedExitCode.HasValue) { exitCode = (ExitCode)failure.SuggestedExitCode.Value; } _logger.LogError(error.ToString()); } else { _logger.LogError($"Failed to install the application"); } return exitCode; } _logger.LogInformation($"Application '{appBundleInfo.AppName}' was installed successfully on '{device.Name}'"); return ExitCode.SUCCESS; } protected virtual async Task UninstallApp(TestTarget target, string bundleIdentifier, IDevice device, bool isPreparation, CancellationToken cancellationToken) { if (isPreparation) { _logger.LogInformation($"Uninstalling any previous instance of '{bundleIdentifier}' from '{device.Name}'"); } else { _logger.LogInformation($"Uninstalling the application '{bundleIdentifier}' from '{device.Name}'"); } ProcessExecutionResult uninstallResult = target.IsSimulator() ? await _appUninstaller.UninstallSimulatorApp((ISimulatorDevice)device, bundleIdentifier, cancellationToken) : await _appUninstaller.UninstallDeviceApp((IHardwareDevice)device, bundleIdentifier, cancellationToken); if (uninstallResult.Succeeded) { _logger.LogInformation($"Application '{bundleIdentifier}' was uninstalled successfully"); return ExitCode.SUCCESS; } // We try to uninstall app before each run to clear it from the device // For those cases, we don't care about the result if (isPreparation) { _logger.LogDebug($"Preemptive uninstallation of application {(uninstallResult.TimedOut ? "timed out" : "failed")}"); } else { if (target.IsSimulator() && uninstallResult.ExitCode == 165) { // When uninstallation returns 165, the simulator is in a weird state where it cannot be booted and running an app later will fail _logger.LogCritical($"Failed to uninstall the application - bad simulator state detected!"); return ExitCode.SIMULATOR_FAILURE; } else { _logger.LogError($"Failed to uninstall the app bundle! Check logs for more details!"); } } return ExitCode.GENERAL_FAILURE; } protected virtual async Task CleanUpSimulators(IDevice device, IDevice? companionDevice) { try { var simulator = (ISimulatorDevice)device; _logger.LogInformation($"Cleaning up simulator '{device.Name}'"); await simulator.KillEverything(_mainLog); if (companionDevice != null) { _logger.LogInformation($"Cleaning up companion simulator '{companionDevice.Name}'"); var companionSimulator = (ISimulatorDevice)companionDevice; await companionSimulator.KillEverything(_mainLog); } } finally { _logger.LogInformation("Simulators cleaned up"); } } protected async Task GetAppBundleFromId(TestTargetOs target, IDevice device, string bundleIdentifier, CancellationToken cancellationToken) { // We can exchange bundle ID for path where the bundle is on the simulator and get that if (device is ISimulatorDevice simulator) { _logger.LogInformation($"Querying simulator for app bundle information.."); await simulator.Boot(_mainLog, cancellationToken); var appBundlePath = await simulator.GetAppBundlePath(_mainLog, bundleIdentifier, cancellationToken); return await GetAppBundleFromPath(target, appBundlePath, cancellationToken); } // We're unable to do this for real devices / or MacCatalyst // It is not ideal but doesn't matter much at the moment as we don't need the full list of properties there _logger.LogDebug("Supplemented full app bundle information with bundle identifier"); return AppBundleInformation.FromBundleId(bundleIdentifier); } protected Task GetAppBundleFromPath(TestTargetOs target, string appBundlePath, CancellationToken cancellationToken) { appBundlePath = Path.GetFullPath(appBundlePath); _logger.LogInformation($"Getting app bundle information from '{appBundlePath}'.."); return _appBundleInformationParser.ParseFromAppBundle(appBundlePath, target.Platform, _mainLog, cancellationToken); } protected static bool IsLldbEnabled() => File.Exists(s_mlaunchLldbConfigFile); protected static void NotifyUserLldbCommand(ILogger logger, string line) { if (!line.Contains("mtouch-lldb-prep-cmds")) { return; } // let the user know the command to execute. Might change in mlaunch so trust the log var sb = new StringBuilder(); sb.AppendLine("LLDB debugging is enabled."); sb.AppendLine("You must now execute:"); sb.AppendLine(line.Substring(line.IndexOf("lldb", StringComparison.Ordinal))); logger.LogInformation(sb.ToString()); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/Orchestration/InstallOrchestrator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface IInstallOrchestrator { Task OrchestrateInstall( TestTargetOs target, string? deviceName, string appPackagePath, TimeSpan timeout, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, CancellationToken cancellationToken); } /// /// This orchestrator implements the `install` command flow. /// public class InstallOrchestrator : BaseOrchestrator, IInstallOrchestrator { public InstallOrchestrator( IAppInstaller appInstaller, IAppUninstaller appUninstaller, IAppBundleInformationParser appBundleInformationParser, IDeviceFinder deviceFinder, ILogger consoleLogger, ILogs logs, IFileBackedLog mainLog, IErrorKnowledgeBase errorKnowledgeBase, IDiagnosticsData diagnosticsData, IHelpers helpers) : base(appBundleInformationParser, appInstaller, appUninstaller, deviceFinder, consoleLogger, logs, mainLog, errorKnowledgeBase, diagnosticsData, helpers) { } public async Task OrchestrateInstall( TestTargetOs target, string? deviceName, string appPackagePath, TimeSpan timeout, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, CancellationToken cancellationToken) { Task GetAppBundleInfo(TestTargetOs target, IDevice device, CancellationToken ct) => GetAppBundleFromPath(target, appPackagePath, ct); static Task ExecuteMacCatalystApp(AppBundleInformation appBundleInfo) => throw new InvalidOperationException("install command not available on maccatalyst"); static Task ExecuteApp(AppBundleInformation appBundleInfo, IDevice device, IDevice? companionDevice) => Task.FromResult(ExitCode.SUCCESS); // no-op var result = await OrchestrateOperation( target, deviceName, includeWirelessDevices, resetSimulator, enableLldb, GetAppBundleInfo, ExecuteMacCatalystApp, ExecuteApp, cancellationToken); if (cancellationToken.IsCancellationRequested) { return ExitCode.PACKAGE_INSTALLATION_TIMEOUT; } return result; } protected override Task CleanUpSimulators(IDevice device, IDevice? companionDevice) => Task.CompletedTask; // no-op so that we don't remove the app after (reset will only clean it up before) protected override Task UninstallApp(TestTarget target, string bundleIdentifier, IDevice device, bool isPreparation, CancellationToken cancellationToken) { // For the installation, we want to uninstall during preparation only if (isPreparation) { return base.UninstallApp(target, bundleIdentifier, device, isPreparation, cancellationToken); } return Task.FromResult(ExitCode.SUCCESS); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/Orchestration/JustRunOrchestrator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface IJustRunOrchestrator { Task OrchestrateRun( string bundleIdentifier, TestTargetOs target, string? deviceName, TimeSpan timeout, TimeSpan launchTimeout, int expectedExitCode, bool includeWirelessDevices, bool enableLldb, bool signalAppEnd, bool waitForExit, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken); } /// /// This orchestrator implements the `just-run` command flow. /// In this flow we spawn the application and do not expect TestRunner inside. /// We only try to detect the exit code after the app run is finished. /// public class JustRunOrchestrator : RunOrchestrator, IJustRunOrchestrator { public JustRunOrchestrator( IAppBundleInformationParser appBundleInformationParser, IAppInstaller appInstaller, IAppUninstaller appUninstaller, IAppRunnerFactory appRunnerFactory, IDeviceFinder deviceFinder, IiOSExitCodeDetector iOSExitCodeDetector, IMacCatalystExitCodeDetector macCatalystExitCodeDetector, ILogger consoleLogger, ILogs logs, IFileBackedLog mainLog, IErrorKnowledgeBase errorKnowledgeBase, IDiagnosticsData diagnosticsData, IHelpers helpers) : base(appBundleInformationParser, appInstaller, appUninstaller, appRunnerFactory, deviceFinder, iOSExitCodeDetector, macCatalystExitCodeDetector, consoleLogger, logs, mainLog, errorKnowledgeBase, diagnosticsData, helpers) { } Task IJustRunOrchestrator.OrchestrateRun( string bundleIdentifier, TestTargetOs target, string? deviceName, TimeSpan timeout, TimeSpan launchTimeout, int expectedExitCode, bool includeWirelessDevices, bool enableLldb, bool signalAppEnd, bool waitForExit, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken) => OrchestrateRun( (target, device, cancellationToken) => GetAppBundleFromId(target, device, bundleIdentifier, cancellationToken), target, deviceName, timeout: timeout, launchTimeout: launchTimeout, expectedExitCode, includeWirelessDevices: includeWirelessDevices, resetSimulator: false, // No simulator reset for just- commands enableLldb: enableLldb, signalAppEnd: signalAppEnd, waitForExit: waitForExit, environmentalVariables, passthroughArguments, cancellationToken); protected override Task CleanUpSimulators(IDevice device, IDevice? companionDevice) => Task.CompletedTask; // no-op so that we don't remove the app after (reset will only clean it up before) protected override Task InstallApp(AppBundleInformation appBundleInfo, IDevice device, TestTargetOs target, CancellationToken cancellationToken) => Task.FromResult(ExitCode.SUCCESS); // no-op - we only want to run the app protected override Task UninstallApp(TestTarget target, string bundleIdentifier, IDevice device, bool isPreparation, CancellationToken cancellationToken) => Task.FromResult(ExitCode.SUCCESS); // no-op - we only want to run the app } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/Orchestration/JustTestOrchestrator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface IJustTestOrchestrator { Task OrchestrateTest( string bundleIdentifier, TestTargetOs target, string? deviceName, TimeSpan timeout, TimeSpan launchTimeout, CommunicationChannel communicationChannel, XmlResultJargon xmlResultJargon, IEnumerable singleMethodFilters, IEnumerable classMethodFilters, bool includeWirelessDevices, bool enableLldb, bool signalAppEnd, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken); } /// /// This orchestrator implements the `just-test` command flow. /// This is the same as `test` except we only run an already installed application and /// we don't prepare the device or clean up. /// In this flow we need to connect to the running application over TCP and receive /// the test results. We also need to watch timeouts better and parse the results /// more comprehensively. /// public class JustTestOrchestrator : TestOrchestrator, IJustTestOrchestrator { public JustTestOrchestrator( IAppBundleInformationParser appBundleInformationParser, IAppInstaller appInstaller, IAppUninstaller appUninstaller, IAppTesterFactory appTesterFactory, IDeviceFinder deviceFinder, ILogger consoleLogger, ILogs logs, IFileBackedLog mainLog, IErrorKnowledgeBase errorKnowledgeBase, IDiagnosticsData diagnosticsData, IHelpers helpers) : base(appBundleInformationParser, appInstaller, appUninstaller, appTesterFactory, deviceFinder, consoleLogger, logs, mainLog, errorKnowledgeBase, diagnosticsData, helpers) { } Task IJustTestOrchestrator.OrchestrateTest( string bundleIdentifier, TestTargetOs target, string? deviceName, TimeSpan timeout, TimeSpan launchTimeout, CommunicationChannel communicationChannel, XmlResultJargon xmlResultJargon, IEnumerable singleMethodFilters, IEnumerable classMethodFilters, bool includeWirelessDevices, bool enableLldb, bool signalAppEnd, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken) => OrchestrateTest( (target, device, ct) => GetAppBundleFromId(target, device, bundleIdentifier, ct), target, deviceName, timeout, launchTimeout, communicationChannel, xmlResultJargon, singleMethodFilters, classMethodFilters, includeWirelessDevices, resetSimulator: false, // No simulator reset for just- commands enableLldb, signalAppEnd, environmentalVariables, passthroughArguments, cancellationToken); protected override Task CleanUpSimulators(IDevice device, IDevice? companionDevice) => Task.CompletedTask; // no-op so that we don't remove the app after (reset will only clean it up before) protected override Task InstallApp(AppBundleInformation appBundleInfo, IDevice device, TestTargetOs target, CancellationToken cancellationToken) => Task.FromResult(ExitCode.SUCCESS); // no-op - we only want to run the app protected override Task UninstallApp(TestTarget target, string bundleIdentifier, IDevice device, bool isPreparation, CancellationToken cancellationToken) => Task.FromResult(ExitCode.SUCCESS); // no-op - we only want to run the app } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/Orchestration/RunOrchestrator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface IRunOrchestrator { Task OrchestrateRun( string appBundlePath, TestTargetOs target, string? deviceName, TimeSpan timeout, TimeSpan launchTimeout, int expectedExitCode, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, bool signalAppEnd, bool waitForExit, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken); } /// /// This orchestrator implements the `run` command flow. /// In this flow we spawn the application and do not expect TestRunner inside. /// We only try to detect the exit code after the app run is finished. /// public class RunOrchestrator : BaseOrchestrator, IRunOrchestrator { private readonly IiOSExitCodeDetector _iOSExitCodeDetector; private readonly IMacCatalystExitCodeDetector _macCatalystExitCodeDetector; private readonly ILogger _logger; private readonly ILogs _logs; private readonly IErrorKnowledgeBase _errorKnowledgeBase; private readonly IAppRunner _appRunner; private bool _waitForExit = true; public RunOrchestrator( IAppBundleInformationParser appBundleInformationParser, IAppInstaller appInstaller, IAppUninstaller appUninstaller, IAppRunnerFactory appRunnerFactory, IDeviceFinder deviceFinder, IiOSExitCodeDetector iOSExitCodeDetector, IMacCatalystExitCodeDetector macCatalystExitCodeDetector, ILogger consoleLogger, ILogs logs, IFileBackedLog mainLog, IErrorKnowledgeBase errorKnowledgeBase, IDiagnosticsData diagnosticsData, IHelpers helpers) : base(appBundleInformationParser, appInstaller, appUninstaller, deviceFinder, consoleLogger, logs, mainLog, errorKnowledgeBase, diagnosticsData, helpers) { _iOSExitCodeDetector = iOSExitCodeDetector ?? throw new ArgumentNullException(nameof(iOSExitCodeDetector)); _macCatalystExitCodeDetector = macCatalystExitCodeDetector ?? throw new ArgumentNullException(nameof(macCatalystExitCodeDetector)); _logger = consoleLogger ?? throw new ArgumentNullException(nameof(consoleLogger)); _logs = logs ?? throw new ArgumentNullException(nameof(logs)); _errorKnowledgeBase = errorKnowledgeBase ?? throw new ArgumentNullException(nameof(errorKnowledgeBase)); // Only add the extra callback if we do know that the feature was indeed enabled Action? logCallback = IsLldbEnabled() ? (l) => NotifyUserLldbCommand(_logger, l) : null; _appRunner = appRunnerFactory.Create(mainLog, logs, logCallback); } public Task OrchestrateRun( string appBundlePath, TestTargetOs target, string? deviceName, TimeSpan timeout, TimeSpan launchTimeout, int expectedExitCode, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, bool signalAppEnd, bool waitForExit, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken) => OrchestrateRun( (target, device, ct) => GetAppBundleFromPath(target, appBundlePath, ct), target, deviceName, timeout: timeout, launchTimeout: launchTimeout, expectedExitCode, includeWirelessDevices: includeWirelessDevices, resetSimulator: resetSimulator, enableLldb: enableLldb, signalAppEnd: signalAppEnd, waitForExit: waitForExit, environmentalVariables, passthroughArguments, cancellationToken); protected override Task UninstallApp(TestTarget target, string bundleIdentifier, IDevice device, bool isPreparation, CancellationToken cancellationToken) { if (!_waitForExit && !isPreparation) { return Task.FromResult(ExitCode.SUCCESS); } return base.UninstallApp(target, bundleIdentifier, device, isPreparation, cancellationToken); } protected override Task CleanUpSimulators(IDevice device, IDevice? companionDevice) { if (!_waitForExit) { return Task.FromResult(ExitCode.SUCCESS); } return base.CleanUpSimulators(device, companionDevice); } protected async Task OrchestrateRun( GetAppBundleInfoFunc getAppBundleInfo, TestTargetOs target, string? deviceName, TimeSpan timeout, TimeSpan launchTimeout, int expectedExitCode, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, bool signalAppEnd, bool waitForExit, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken) { if (signalAppEnd && !waitForExit) { throw new InvalidOperationException("Cannot receive app end signal without waiting for it to exit"); } _waitForExit = waitForExit; // The --launch-timeout option must start counting now and not complete before we start running tests to succeed. // After then, this timeout must not interfere. // Tests start running inside of ExecuteApp() which means we have to time-constrain all operations happening inside // OrchestrateRun() that happen before we start the app execution. // We will achieve this by sending a special cancellation token to OrchestrateRun() and only cancel if it in case // we didn't manage to start the app run until then. using var launchTimeoutCancellation = new CancellationTokenSource(); var appRunStarted = false; var task = Task.Delay(launchTimeout < timeout ? launchTimeout : timeout, cancellationToken).ContinueWith(t => { if (!appRunStarted) { _logger.LogError("Cancelling the run as application failed to launch in time"); launchTimeoutCancellation.Cancel(); } }, cancellationToken); using var launchTimeoutCancellationToken = CancellationTokenSource.CreateLinkedTokenSource( launchTimeoutCancellation.Token, cancellationToken); Task ExecuteMacCatalystApp(AppBundleInformation appBundleInfo) { appRunStarted = true; return this.ExecuteMacCatalystApp( appBundleInfo, timeout, expectedExitCode, signalAppEnd, waitForExit, environmentalVariables, passthroughArguments, cancellationToken); } Task ExecuteApp(AppBundleInformation appBundleInfo, IDevice device, IDevice? companionDevice) { // Exit code detection is broken on the newest iOS // More details here: https://github.com/dotnet/xharness/issues/819 if (expectedExitCode != 0) { var os = target.Platform.IsSimulator() ? device.OSVersion.Split(' ', 2).Last() : device.OSVersion; if (Version.TryParse(os, out var version) && version.Major > 14) { _logger.LogWarning( "Exit code detection is not working on iOS/tvOS 15+ so the run will fail to match it with the expected value"); } } appRunStarted = true; return this.ExecuteApp( appBundleInfo, target, device, companionDevice, timeout, expectedExitCode, signalAppEnd: signalAppEnd, waitForExit: waitForExit, environmentalVariables, passthroughArguments, cancellationToken); } return await OrchestrateOperation( target, deviceName, includeWirelessDevices: includeWirelessDevices, resetSimulator: resetSimulator, enableLldb: enableLldb, getAppBundleInfo, ExecuteMacCatalystApp, ExecuteApp, launchTimeoutCancellationToken.Token); } private async Task ExecuteApp( AppBundleInformation appBundleInfo, TestTargetOs target, IDevice device, IDevice? companionDevice, TimeSpan timeout, int expectedExitCode, bool signalAppEnd, bool waitForExit, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken) { if (signalAppEnd && target.Platform.IsSimulator()) { _logger.LogWarning("The --signal-app-end option is used for device tests and has no effect on simulators"); } _logger.LogInformation($"Starting '{appBundleInfo.AppName}' on '{device.Name}'"); ProcessExecutionResult result = await _appRunner.RunApp( appBundleInfo, target, device, companionDevice, timeout, signalAppEnd: signalAppEnd, waitForExit: waitForExit, passthroughArguments, environmentalVariables, cancellationToken); if (!waitForExit) { _logger.LogInformation("Not waiting for app to exit"); return ExitCode.SUCCESS; } return ParseResult(_iOSExitCodeDetector, expectedExitCode, appBundleInfo, result); } private async Task ExecuteMacCatalystApp( AppBundleInformation appBundleInfo, TimeSpan timeout, int expectedExitCode, bool signalAppEnd, bool waitForExit, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken) { _logger.LogInformation($"Starting '{appBundleInfo.AppName}' on MacCatalyst"); ProcessExecutionResult result = await _appRunner.RunMacCatalystApp( appBundleInfo, timeout, signalAppEnd: signalAppEnd, waitForExit: waitForExit, passthroughArguments, environmentalVariables, cancellationToken: cancellationToken); if (!waitForExit) { _logger.LogInformation("Not waiting for app to exit"); return ExitCode.SUCCESS; } return ParseResult(_macCatalystExitCodeDetector, expectedExitCode, appBundleInfo, result); } private ExitCode ParseResult( IExitCodeDetector exitCodeDetector, int expectedExitCode, AppBundleInformation appBundleInfo, ProcessExecutionResult result) { if (result.TimedOut) { _logger.LogError($"App run has timed out"); return ExitCode.TIMED_OUT; } // On iOS 18 and later, mlaunch returns exit code 1 with the following error message: // "Failed to execute 'devicectl': returned the exit code ." if (!result.Succeeded && result.ExitCode != expectedExitCode) { _logger.LogError($"App run has failed. mlaunch exited with {result.ExitCode}"); return ExitCode.APP_LAUNCH_FAILURE; } var logs = _logs.Where(log => log.Description == LogType.SystemLog.ToString() || log.Description == LogType.ApplicationLog.ToString()).ToList(); if (!logs.Any()) { _logger.LogError("Application has finished but no system log found. Failed to determine the exit code!"); return ExitCode.RETURN_CODE_NOT_SET; } int? exitCode = null; foreach (var log in logs) { try { exitCode = exitCodeDetector.DetectExitCode(appBundleInfo, log); if (exitCode.HasValue) { _logger.LogDebug($"Detected exit code {exitCode.Value} from {log.FullPath}"); break; } if (result.ExitCode != 0) { exitCode = result.ExitCode; } _logger.LogDebug($"Failed to determine the exit code from {log.FullPath}"); } catch (Exception e) { _logger.LogDebug($"Failed to determine the exit code from {log.FullPath}:{Environment.NewLine}{e.Message}"); } } if (exitCode is null) { if (expectedExitCode != 0) { _logger.LogError("Application has finished but XHarness failed to determine its exit code!"); return ExitCode.RETURN_CODE_NOT_SET; } _logger.LogInformation("App run ended, no abnormal exit code detected (0 assumed)"); exitCode = 0; } else { _logger.LogInformation($"App run ended with {exitCode}"); } if (expectedExitCode != exitCode) { _logger.LogError($"Application has finished with exit code {exitCode} but {expectedExitCode} was expected"); var cliExitCode = ExitCode.GENERAL_FAILURE; foreach (var log in _logs) { if (_errorKnowledgeBase.IsKnownTestIssue(log, out var failure)) { _logger.LogError(failure.HumanMessage); if (failure.SuggestedExitCode.HasValue) { cliExitCode = (ExitCode)failure.SuggestedExitCode.Value; } break; } } return cliExitCode; } _logger.LogInformation("Application has finished with exit code: " + exitCode + (expectedExitCode != 0 ? " (as expected)" : null)); return ExitCode.SUCCESS; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/Orchestration/SimulatorResetOrchestrator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface ISimulatorResetOrchestrator { Task OrchestrateSimulatorReset( TestTargetOs target, string? deviceName, TimeSpan timeout, CancellationToken cancellationToken); } /// /// This orchestrator implements the `uninstall` command flow. /// public class SimulatorResetOrchestrator : BaseOrchestrator, ISimulatorResetOrchestrator { private readonly ILogger _consoleLogger; public SimulatorResetOrchestrator( IAppInstaller appInstaller, IAppUninstaller appUninstaller, IDeviceFinder deviceFinder, ILogger consoleLogger, ILogs logs, IFileBackedLog mainLog, IErrorKnowledgeBase errorKnowledgeBase, IDiagnosticsData diagnosticsData, IHelpers helpers) : base(new FakeAppBundleInformationParser(), appInstaller, appUninstaller, deviceFinder, consoleLogger, logs, mainLog, errorKnowledgeBase, diagnosticsData, helpers) { _consoleLogger = consoleLogger; } public Task OrchestrateSimulatorReset( TestTargetOs target, string? deviceName, TimeSpan timeout, CancellationToken cancellationToken) { if (!target.Platform.IsSimulator() || target.Platform.ToRunMode() == RunMode.MacOS) { _consoleLogger.LogError($"The simulator reset action requires a simulator target while {target.AsString()} specified"); return Task.FromResult(ExitCode.INVALID_ARGUMENTS); } static Task ExecuteMacCatalystApp(AppBundleInformation appBundleInfo) => throw new InvalidOperationException("reset-simulator command not available on maccatalyst"); static Task ExecuteApp(AppBundleInformation appBundleInfo, IDevice device, IDevice? companionDevice) => Task.FromResult(ExitCode.SUCCESS); // no-op return OrchestrateOperation( target, deviceName, includeWirelessDevices: false, resetSimulator: true, enableLldb: false, (_, __, ___) => Task.FromResult(AppBundleInformation.FromBundleId(string.Empty)), // This is not really needed for this command ExecuteMacCatalystApp, ExecuteApp, cancellationToken); } protected override Task InstallApp(AppBundleInformation appBundleInfo, IDevice device, TestTargetOs target, CancellationToken cancellationToken) => Task.FromResult(ExitCode.SUCCESS); // no-op - we only want to reset the simulator protected override Task UninstallApp(TestTarget target, string bundleIdentifier, IDevice device, bool isPreparation, CancellationToken cancellationToken) => Task.FromResult(ExitCode.SUCCESS); // no-op - we only want to reset the simulator protected override Task CleanUpSimulators(IDevice device, IDevice? companionDevice) => Task.CompletedTask; // no-op - reset is enough, clean-up is not needed afterwards // The reset-simulator command doesn't (as oposed to the others) work with any app bundle specifically so we have to work around this part private class FakeAppBundleInformationParser : IAppBundleInformationParser { public Task ParseFromAppBundle(string appPackagePath, TestTarget target, ILog log, CancellationToken cancellationToken = default) => Task.FromResult(new AppBundleInformation(string.Empty, string.Empty, string.Empty, string.Empty, false)); public Task ParseFromProject(string projectFilePath, TestTarget target, string buildConfiguration) => Task.FromResult(new AppBundleInformation(string.Empty, string.Empty, string.Empty, string.Empty, false)); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/Orchestration/TestOrchestrator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface ITestOrchestrator { Task OrchestrateTest( string appBundlePath, TestTargetOs target, string? deviceName, TimeSpan timeout, TimeSpan launchTimeout, CommunicationChannel communicationChannel, XmlResultJargon xmlResultJargon, IEnumerable singleMethodFilters, IEnumerable classMethodFilters, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, bool signalAppEnd, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken); } /// /// Common ancestor for `test` and `just-test` orchestrators. /// public class TestOrchestrator : BaseOrchestrator, ITestOrchestrator { private readonly IAppTesterFactory _appTesterFactory; private readonly ILogger _logger; private readonly ILogs _logs; private readonly IFileBackedLog _mainLog; private readonly IErrorKnowledgeBase _errorKnowledgeBase; public TestOrchestrator( IAppBundleInformationParser appBundleInformationParser, IAppInstaller appInstaller, IAppUninstaller appUninstaller, IAppTesterFactory appTesterFactory, IDeviceFinder deviceFinder, ILogger consoleLogger, ILogs logs, IFileBackedLog mainLog, IErrorKnowledgeBase errorKnowledgeBase, IDiagnosticsData diagnosticsData, IHelpers helpers) : base(appBundleInformationParser, appInstaller, appUninstaller, deviceFinder, consoleLogger, logs, mainLog, errorKnowledgeBase, diagnosticsData, helpers) { _appTesterFactory = appTesterFactory ?? throw new ArgumentNullException(nameof(appTesterFactory)); _logger = consoleLogger ?? throw new ArgumentNullException(nameof(consoleLogger)); _logs = logs ?? throw new ArgumentNullException(nameof(logs)); _mainLog = mainLog ?? throw new ArgumentNullException(nameof(mainLog)); _errorKnowledgeBase = errorKnowledgeBase ?? throw new ArgumentNullException(nameof(errorKnowledgeBase)); } public Task OrchestrateTest( string appBundlePath, TestTargetOs target, string? deviceName, TimeSpan timeout, TimeSpan launchTimeout, CommunicationChannel communicationChannel, XmlResultJargon xmlResultJargon, IEnumerable singleMethodFilters, IEnumerable classMethodFilters, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, bool signalAppEnd, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken) => OrchestrateTest( (target, device, ct) => GetAppBundleFromPath(target, appBundlePath, ct), target, deviceName, timeout, launchTimeout, communicationChannel, xmlResultJargon, singleMethodFilters, classMethodFilters, includeWirelessDevices, resetSimulator: resetSimulator, enableLldb, signalAppEnd, environmentalVariables, passthroughArguments, cancellationToken); public virtual async Task OrchestrateTest( GetAppBundleInfoFunc getAppBundlePath, TestTargetOs target, string? deviceName, TimeSpan timeout, TimeSpan launchTimeout, CommunicationChannel communicationChannel, XmlResultJargon xmlResultJargon, IEnumerable singleMethodFilters, IEnumerable classMethodFilters, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, bool signalAppEnd, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, CancellationToken cancellationToken) { // The --launch-timeout option must start counting now and not complete before we start running tests to succeed. // After then, this timeout must not interfere. // Tests start running inside of ExecuteApp() which means we have to time-constrain all operations happening inside // OrchestrateRun() that happen before we start the app execution. // We will achieve this by sending a special cancellation token to OrchestrateRun() and only cancel if it in case // we didn't manage to start the app run until then. using var launchTimeoutCancellation = new CancellationTokenSource(); var appRunStarted = false; var shorterTimeout = launchTimeout < timeout ? launchTimeout : timeout; var task = Task.Delay(shorterTimeout, cancellationToken).ContinueWith(t => { if (!appRunStarted) { _logger.LogError($"Cancelling the run after {Math.Ceiling(shorterTimeout.TotalSeconds)} seconds as application failed to launch in time"); launchTimeoutCancellation.Cancel(); } }, cancellationToken); // We also want to shrink the launch timeout by whatever elapsed before we get to ExecuteApp Stopwatch watch = Stopwatch.StartNew(); using var launchTimeoutCancellationToken = CancellationTokenSource.CreateLinkedTokenSource( launchTimeoutCancellation.Token, cancellationToken); Task ExecuteMacCatalystApp(AppBundleInformation appBundleInfo) { appRunStarted = true; return this.ExecuteMacCatalystApp( appBundleInfo, timeout, launchTimeout - watch.Elapsed, communicationChannel, xmlResultJargon, singleMethodFilters, classMethodFilters, environmentalVariables, passthroughArguments, signalAppEnd, cancellationToken); // This cancellation token doesn't include the launch-timeout one } Task ExecuteApp(AppBundleInformation appBundleInfo, IDevice device, IDevice? companionDevice) { appRunStarted = true; return this.ExecuteApp( appBundleInfo, target, device, companionDevice, timeout, launchTimeout - watch.Elapsed, communicationChannel, xmlResultJargon, singleMethodFilters, classMethodFilters, environmentalVariables, passthroughArguments, signalAppEnd, cancellationToken); // This cancellation token doesn't include the launch-timeout one } return await OrchestrateOperation( target, deviceName, includeWirelessDevices, resetSimulator, enableLldb, getAppBundlePath, ExecuteMacCatalystApp, ExecuteApp, launchTimeoutCancellationToken.Token); } private async Task ExecuteApp( AppBundleInformation appBundleInfo, TestTargetOs target, IDevice device, IDevice? companionDevice, TimeSpan timeout, TimeSpan launchTimeout, CommunicationChannel communicationChannel, XmlResultJargon xmlResultJargon, IEnumerable singleMethodFilters, IEnumerable classMethodFilters, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, bool signalAppEnd, CancellationToken cancellationToken) { bool versionParsed = Version.TryParse(device.OSVersion, out var version); // iOS 14+ devices do not allow local network access and won't work unless the user confirms a dialog on the screen // https://developer.apple.com/forums/thread/663858 if (versionParsed && version!.Major >= 14 && target.Platform.ToRunMode() == RunMode.iOS && communicationChannel == CommunicationChannel.Network) { _logger.LogWarning( "Applications need user permission for communication over local network on iOS 14 and newer." + Environment.NewLine + "Either confirm a dialog on the device after the application launches or use the USB tunnel communication channel." + Environment.NewLine + "Test run might fail if permission is not granted. Permission is valid until app is uninstalled."); } if (signalAppEnd && target.Platform.IsSimulator()) { _logger.LogWarning("The --signal-app-end option is used for device tests and has no effect on simulators"); } _logger.LogInformation("Starting test run for " + appBundleInfo.BundleIdentifier + ".."); var appTester = GetAppTester(communicationChannel, target.Platform.IsSimulator(), version); (TestExecutingResult testResult, string resultMessage) = await appTester.TestApp( appBundleInfo, target, device, companionDevice, timeout, launchTimeout, signalAppEnd, passthroughArguments, environmentalVariables, xmlResultJargon, skippedMethods: singleMethodFilters?.ToArray(), skippedTestClasses: classMethodFilters?.ToArray(), cancellationToken: cancellationToken); ExitCode exitCode = ParseResult(testResult, resultMessage, appTester.ListenerConnected); // Copy application logs to the main log for better failure investigation. CopyLogsToMainLog(); return exitCode; } private async Task ExecuteMacCatalystApp( AppBundleInformation appBundleInfo, TimeSpan timeout, TimeSpan launchTimeout, CommunicationChannel communicationChannel, XmlResultJargon xmlResultJargon, IEnumerable singleMethodFilters, IEnumerable classMethodFilters, IReadOnlyCollection<(string, string?)> environmentalVariables, IEnumerable passthroughArguments, bool signalAppEnd, CancellationToken cancellationToken) { var appTester = GetAppTester(communicationChannel, TestTarget.MacCatalyst.IsSimulator(), null); (TestExecutingResult testResult, string resultMessage) = await appTester.TestMacCatalystApp( appBundleInfo, timeout, launchTimeout, signalAppEnd, passthroughArguments, environmentalVariables, xmlResultJargon, skippedMethods: singleMethodFilters?.ToArray(), skippedTestClasses: classMethodFilters?.ToArray(), cancellationToken: cancellationToken); ExitCode exitCode = ParseResult(testResult, resultMessage, appTester.ListenerConnected); // Copy system log to the main log — MacCatalyst output goes to SystemLog, not ApplicationLog CopyLogsToMainLog(isMacCatalyst: true); return exitCode; } private IAppTester GetAppTester(CommunicationChannel communicationChannel, bool isSimulator, Version? osVersion) { // Only add the extra callback if we do know that the feature was indeed enabled Action? logCallback = IsLldbEnabled() ? (l) => NotifyUserLldbCommand(_logger, l) : null; return _appTesterFactory.Create(communicationChannel, isSimulator, _mainLog, _logs, logCallback, osVersion); } private ExitCode ParseResult(TestExecutingResult testResult, string resultMessage, bool listenerConnected) { string newLine = Environment.NewLine; const string checkLogsMessage = "Check logs for more information"; bool tcpErrorFound = false; ExitCode LogProblem(string message, ExitCode defaultExitCode) { foreach (var log in _logs) { if (_errorKnowledgeBase.IsKnownTestIssue(log, out var issue)) { if (!listenerConnected && issue.SuggestedExitCode.HasValue && (ExitCode)issue.SuggestedExitCode.Value == ExitCode.TCP_CONNECTION_FAILED) { tcpErrorFound = true; } else { _logger.LogError(message + newLine + issue.HumanMessage); return issue.SuggestedExitCode.HasValue ? (ExitCode)issue.SuggestedExitCode.Value : defaultExitCode; } } } if (resultMessage != null) { _logger.LogError(message + newLine + resultMessage + newLine + newLine + checkLogsMessage); } else { _logger.LogError(message + newLine + checkLogsMessage); } // TCP errors are encounter all the time but they are not always the cause of the failure // If the app crashed, TCP_CONNECTION_FAILED and there was not other exit code we will return TCP_CONNECTION_FAILED if (defaultExitCode == ExitCode.APP_CRASH && tcpErrorFound) { return ExitCode.TCP_CONNECTION_FAILED; } return defaultExitCode; } switch (testResult) { case TestExecutingResult.Succeeded: _logger.LogInformation($"Application finished the test run successfully"); _logger.LogInformation(resultMessage); return ExitCode.SUCCESS; case TestExecutingResult.Failed: _logger.LogInformation($"Application finished the test run successfully with some failed tests"); _logger.LogInformation(resultMessage); return ExitCode.TESTS_FAILED; case TestExecutingResult.LaunchFailure: return LogProblem("Failed to launch the application", ExitCode.APP_LAUNCH_FAILURE); case TestExecutingResult.Crashed: return LogProblem("Application test run crashed", ExitCode.APP_CRASH); case TestExecutingResult.LaunchTimedOut: _logger.LogError("Application launch timed out before the test execution has started"); return ExitCode.APP_LAUNCH_TIMEOUT; case TestExecutingResult.TimedOut: _logger.LogWarning($"Application test run timed out"); return ExitCode.TIMED_OUT; default: _logger.LogError($"Application test run ended in an unexpected way: '{testResult}'" + newLine + (resultMessage != null ? resultMessage + newLine + newLine : null) + checkLogsMessage); return ExitCode.GENERAL_FAILURE; } } /// /// Copy system and application logs to the main log for better failure investigation. /// private void CopyLogsToMainLog(bool isMacCatalyst = false) { // ApplicationLog: app console output captured via simctl (iOS/tvOS simulators, devices) // SystemLog: macOS system log where MacCatalyst app output goes (runs as native process) var targetLogType = isMacCatalyst ? LogType.SystemLog : LogType.ApplicationLog; var logs = _logs.Where(log => log.Description == targetLogType.ToString()).ToList(); _logger.LogInformation($"Copying {targetLogType} logs to the main log for better failure investigation. Logs count: {logs.Count}."); foreach (var log in logs) { _mainLog.WriteLine($"==================== {log.Description} ===================="); _mainLog.WriteLine($"Log file: {log.FullPath}"); try { // Read and append log content to the main log using var reader = log.GetReader(); while (!reader.EndOfStream) { var logContent = reader.ReadLine(); if (logContent is null) continue; _mainLog.WriteLine(logContent); } } catch (Exception ex) { _mainLog.WriteLine($"Failed to read {log.Description}: {ex.Message}"); } _mainLog.WriteLine($"==================== End of {log.Description} ===================="); _mainLog.WriteLine(string.Empty); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Apple/Orchestration/UninstallOrchestrator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.Apple; public interface IUninstallOrchestrator { Task OrchestrateAppUninstall( string bundleIdentifier, TestTargetOs target, string? deviceName, TimeSpan timeout, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, CancellationToken cancellationToken); } /// /// This orchestrator implements the `uninstall` command flow. /// public class UninstallOrchestrator : BaseOrchestrator, IUninstallOrchestrator { public UninstallOrchestrator( IAppBundleInformationParser appBundleInformationParser, IAppInstaller appInstaller, IAppUninstaller appUninstaller, IDeviceFinder deviceFinder, ILogger consoleLogger, ILogs logs, IFileBackedLog mainLog, IErrorKnowledgeBase errorKnowledgeBase, IDiagnosticsData diagnosticsData, IHelpers helpers) : base(appBundleInformationParser, appInstaller, appUninstaller, deviceFinder, consoleLogger, logs, mainLog, errorKnowledgeBase, diagnosticsData, helpers) { } public Task OrchestrateAppUninstall( string bundleIdentifier, TestTargetOs target, string? deviceName, TimeSpan timeout, bool includeWirelessDevices, bool resetSimulator, bool enableLldb, CancellationToken cancellationToken) { static Task ExecuteMacCatalystApp(AppBundleInformation appBundleInfo) => throw new InvalidOperationException("uninstall command not available on maccatalyst"); static Task ExecuteApp(AppBundleInformation appBundleInfo, IDevice device, IDevice? companionDevice) => Task.FromResult(ExitCode.SUCCESS); // no-op return OrchestrateOperation( target, deviceName, includeWirelessDevices, resetSimulator, enableLldb, (target, device, cancellationToken) => GetAppBundleFromId(target, device, bundleIdentifier, cancellationToken), ExecuteMacCatalystApp, ExecuteApp, cancellationToken); } protected override Task InstallApp(AppBundleInformation appBundleInfo, IDevice device, TestTargetOs target, CancellationToken cancellationToken) => Task.FromResult(ExitCode.SUCCESS); // no-op - we only want to uninstall the app protected override Task UninstallApp(TestTarget target, string bundleIdentifier, IDevice device, bool isPreparation, CancellationToken cancellationToken) { // For the uninstallation, we don't want to uninstall twice so we skip the preparation one if (isPreparation) { return Task.FromResult(ExitCode.SUCCESS); } return base.UninstallApp(target, bundleIdentifier, device, isPreparation, cancellationToken); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/AndroidAdbCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal class AndroidAdbCommandArguments : XHarnessCommandArguments { public TimeoutArgument Timeout { get; set; } = new(TimeSpan.FromMinutes(1)); protected override IEnumerable GetArguments() => new[] { Timeout, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/AndroidArchitecture.cs ================================================ using System; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal enum AndroidArchitecture { X86, X86_64, Arm64_v8a, Armeabi_v7a } internal static class AndroidArchitectureHelper { public static AndroidArchitecture ParseAsAndroidArchitecture(this string target) => target switch { "x86" => AndroidArchitecture.X86, "x86_64" => AndroidArchitecture.X86_64, "arm64-v8a" => AndroidArchitecture.Arm64_v8a, "armeabi-v7a" => AndroidArchitecture.Armeabi_v7a, _ => throw new ArgumentOutOfRangeException(nameof(target)) }; public static string AsString(this AndroidArchitecture arch) => arch switch { AndroidArchitecture.X86 => "x86", AndroidArchitecture.X86_64 => "x86_64", AndroidArchitecture.Arm64_v8a => "arm64-v8a", AndroidArchitecture.Armeabi_v7a => "armeabi-v7a", _ => throw new ArgumentOutOfRangeException(nameof(arch)) }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/AndroidDeviceCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal class AndroidDeviceCommandArguments : XHarnessCommandArguments { public OptionalAppPathArgument AppPackagePath { get; } = new(); public DeviceArchitectureArgument DeviceArchitecture { get; } = new(); public ApiVersionArgument ApiVersion { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { AppPackagePath, DeviceArchitecture, ApiVersion, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/AndroidInstallCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal class AndroidInstallCommandArguments : XHarnessCommandArguments, IAndroidAppRunArguments { public AppPathArgument AppPackagePath { get; } = new(); public PackageNameArgument PackageName { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public LaunchTimeoutArgument LaunchTimeout { get; } = new(TimeSpan.FromMinutes(5)); public DeviceIdArgument DeviceId { get; } = new(); public DeviceArchitectureArgument DeviceArchitecture { get; } = new(); public ApiVersionArgument ApiVersion { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { AppPackagePath, PackageName, OutputDirectory, Timeout, DeviceId, LaunchTimeout, DeviceArchitecture, ApiVersion, }; public override void Validate() { base.Validate(); if (!File.Exists(AppPackagePath)) { throw new ArgumentException($"Couldn't find {AppPackagePath}!"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/AndroidRunCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal class AndroidRunCommandArguments : XHarnessCommandArguments, IAndroidAppRunArguments { public PackageNameArgument PackageName { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public LaunchTimeoutArgument LaunchTimeout { get; } = new(TimeSpan.FromMinutes(5)); public DeviceIdArgument DeviceId { get; } = new(); public ApiVersionArgument ApiVersion { get; } = new(); public InstrumentationNameArgument InstrumentationName { get; } = new(); public InstrumentationArguments InstrumentationArguments { get; } = new(); public ExpectedExitCodeArgument ExpectedExitCode { get; } = new((int)Common.CLI.ExitCode.SUCCESS); public DeviceOutputFolderArgument DeviceOutputFolder { get; } = new(); public WifiArgument Wifi { get; } = new(); public CommandArguments.EnableCoverageArgument EnableCoverage { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { PackageName, OutputDirectory, Timeout, LaunchTimeout, DeviceId, ApiVersion, InstrumentationName, InstrumentationArguments, ExpectedExitCode, DeviceOutputFolder, Wifi, EnableCoverage, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/AndroidStateCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal class AndroidStateCommandArguments : XHarnessCommandArguments { public UseJsonArgument UseJson { get; set; } = new(); protected override IEnumerable GetArguments() => new Argument[] { UseJson, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/AndroidTestCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal class AndroidTestCommandArguments : XHarnessCommandArguments, IAndroidAppRunArguments { public AppPathArgument AppPackagePath { get; } = new(); public PackageNameArgument PackageName { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public LaunchTimeoutArgument LaunchTimeout { get; } = new(TimeSpan.FromMinutes(5)); public DeviceIdArgument DeviceId { get; } = new(); public DeviceArchitectureArgument DeviceArchitecture { get; } = new(); public ApiVersionArgument ApiVersion { get; } = new(); public InstrumentationNameArgument InstrumentationName { get; } = new(); public InstrumentationArguments InstrumentationArguments { get; } = new(); public ExpectedExitCodeArgument ExpectedExitCode { get; } = new((int)Common.CLI.ExitCode.SUCCESS); public DeviceOutputFolderArgument DeviceOutputFolder { get; } = new(); public WifiArgument Wifi { get; } = new(); public CommandArguments.EnableCoverageArgument EnableCoverage { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { AppPackagePath, PackageName, OutputDirectory, Timeout, LaunchTimeout, DeviceArchitecture, DeviceId, ApiVersion, InstrumentationName, InstrumentationArguments, ExpectedExitCode, DeviceOutputFolder, Wifi, EnableCoverage, }; public override void Validate() { base.Validate(); if (!File.Exists(AppPackagePath)) { throw new ArgumentException($"Couldn't find {AppPackagePath}!"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/AndroidUninstallCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal class AndroidUninstallCommandArguments : XHarnessCommandArguments { public PackageNameArgument PackageName { get; } = new(); public DeviceIdArgument DeviceId { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { PackageName, DeviceId, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/Arguments/ApiVersionArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal class ApiVersionArgument : OptionalIntArgument { public ApiVersionArgument() : base("api-version=|api=", "Target a device/emulator with given Android API version (level)") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/Arguments/DeviceArchitectureArgument.cs ================================================ using System; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; /// /// If specified, attempt to run on a compatible attached device, failing if unavailable. /// If not specified, we will open the apk using Zip APIs and guess what's usable based off folders found in under /lib /// internal class DeviceArchitectureArgument : RepeatableArgument { public DeviceArchitectureArgument() : base("device-arch=", "If specified, forces running on a device with given or compatible architecture (x86, x86_64, arm64-v8a or armeabi-v7a). Otherwise inferred from supplied APK") { } public override void Validate() { foreach (var archName in Value) { try { AndroidArchitectureHelper.ParseAsAndroidArchitecture(archName); } catch (ArgumentOutOfRangeException e) { throw new ArgumentException( $"Failed to parse architecture '{archName}'. Available architectures are:" + GetAllowedValues(t => t.AsString()), e); } } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/Arguments/DeviceIdArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; /// /// If specified, attempt to run APK on that device. /// If there is more than one device with required architecture, failing to specify this may cause execution failure. /// internal class DeviceIdArgument : StringArgument { public DeviceIdArgument() : base("device-id=", "Device where APK should be installed") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/Arguments/DeviceOutputFolderArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; /// /// Folder to copy off for output of executing the specified APK /// internal class DeviceOutputFolderArgument : PathArgument { public DeviceOutputFolderArgument() : base("device-out-folder=|dev-out=", "If specified, copy this folder recursively off the device to the path specified by the output directory", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/Arguments/InstrumentationArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; /// /// Passing these arguments as testing options to a test runner /// internal class InstrumentationArguments : Argument> { public InstrumentationArguments() : base("arg=", "Argument to pass to the instrumentation, in form key=value", new Dictionary()) { } public override void Action(string argumentValue) { var argPair = argumentValue.Split('=', 2); if (argPair.Length != 2) { throw new ArgumentException($"The --arg argument expects 'key=value' format. Invalid format found in '{argumentValue}'"); } if (Value.ContainsKey(argPair[0])) { throw new ArgumentException($"Duplicate arg name '{argPair[0]}' found"); } Value.Add(argPair[0].Trim(), argPair[1].Trim()); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/Arguments/InstrumentationNameArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; /// /// If specified, attempt to run instrumentation with this name instead of the default for the supplied APK. /// If a given package has multiple instrumentations, failing to specify this may cause execution failure. /// internal class InstrumentationNameArgument : StringArgument { public InstrumentationNameArgument() : base("instrumentation=|i=", "If specified, attempt to run instrumentation with this name instead of the default for the supplied APK") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/Arguments/LaunchTimeoutArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; /// /// Time to wait for boot completion. /// internal class LaunchTimeoutArgument : TimeSpanArgument { public LaunchTimeoutArgument(TimeSpan defaultValue) : base("launch-timeout=|lt=", $"Time span in the form of \"00:00:00\" or number of seconds to wait for the device to boot to complete. Default is {defaultValue}", defaultValue) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/Arguments/PackageNameArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal class PackageNameArgument : RequiredStringArgument { public PackageNameArgument() : base("package-name=|p=", "Package name contained within the supplied APK") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/Arguments/ShowAdbPathArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal class ShowAdbPathArgument : SwitchArgument { public ShowAdbPathArgument() : base("adb|show-adb-path", "Prints ONLY path to the adb executable XHarness is using", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/Arguments/WifiArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; /// /// Switch on/off wifi on the device. /// internal class WifiArgument : Argument { public WifiArgument() : base("wifi:", "Enable/disable WiFi. WiFi state is ignored by default. If passed without value, 'enable' is assumed", WifiStatus.Unknown) { } public override void Action(string argumentValue) { Value = string.IsNullOrEmpty(argumentValue) ? WifiStatus.Enable : ParseArgument("wifi", argumentValue, invalidValues: WifiStatus.Unknown); } } internal enum WifiStatus { /// /// Not checked by default. /// Unknown, Enable, Disable, } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Android/IAndroidAppRunArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; internal interface IAndroidAppRunArguments { PackageNameArgument PackageName { get; } OutputDirectoryArgument OutputDirectory { get; } TimeoutArgument Timeout { get; } LaunchTimeoutArgument LaunchTimeout { get; } DeviceIdArgument DeviceId { get; } ApiVersionArgument ApiVersion { get; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/AndroidHeadless/AndroidHeadlessInstallCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; internal class AndroidHeadlessInstallCommandArguments : XHarnessCommandArguments, IAndroidHeadlessAppRunArguments { public TestPathArgument TestPath { get; } = new(); public RuntimePathArgument RuntimePath { get; } = new(); public TestAssemblyArgument TestAssembly { get; } = new(); public TestScriptArgument TestScript { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public LaunchTimeoutArgument LaunchTimeout { get; } = new(TimeSpan.FromMinutes(5)); public DeviceIdArgument DeviceId { get; } = new(); public DeviceArchitectureArgument DeviceArchitecture { get; } = new(); public ApiVersionArgument ApiVersion { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { TestPath, RuntimePath, TestAssembly, TestScript, OutputDirectory, Timeout, DeviceId, LaunchTimeout, DeviceArchitecture, ApiVersion, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/AndroidHeadless/AndroidHeadlessRunCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; internal class AndroidHeadlessRunCommandArguments : XHarnessCommandArguments, IAndroidHeadlessAppRunArguments { public TestPathArgument TestPath { get; } = new(); public RuntimePathArgument RuntimePath { get; } = new(); public TestAssemblyArgument TestAssembly { get; } = new(); public TestScriptArgument TestScript { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public LaunchTimeoutArgument LaunchTimeout { get; } = new(TimeSpan.FromMinutes(5)); public DeviceIdArgument DeviceId { get; } = new(); public ApiVersionArgument ApiVersion { get; } = new(); public ExpectedExitCodeArgument ExpectedExitCode { get; } = new((int)Common.CLI.ExitCode.SUCCESS); public DeviceOutputFolderArgument DeviceOutputFolder { get; } = new(); public WifiArgument Wifi { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { TestPath, RuntimePath, TestAssembly, TestScript, OutputDirectory, Timeout, LaunchTimeout, DeviceId, ApiVersion, ExpectedExitCode, DeviceOutputFolder, Wifi, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/AndroidHeadless/AndroidHeadlessTestCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; internal class AndroidHeadlessTestCommandArguments : XHarnessCommandArguments, IAndroidHeadlessAppRunArguments { public TestPathArgument TestPath { get; } = new(); public RuntimePathArgument RuntimePath { get; } = new(); public TestAssemblyArgument TestAssembly { get; } = new(); public TestScriptArgument TestScript { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public LaunchTimeoutArgument LaunchTimeout { get; } = new(TimeSpan.FromMinutes(5)); public DeviceIdArgument DeviceId { get; } = new(); public DeviceArchitectureArgument DeviceArchitecture { get; } = new(); public ApiVersionArgument ApiVersion { get; } = new(); public ExpectedExitCodeArgument ExpectedExitCode { get; } = new((int)Common.CLI.ExitCode.SUCCESS); public WifiArgument Wifi { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { TestPath, RuntimePath, TestAssembly, TestScript, OutputDirectory, Timeout, LaunchTimeout, DeviceArchitecture, DeviceId, ApiVersion, ExpectedExitCode, Wifi, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/AndroidHeadless/AndroidHeadlessUninstallCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; internal class AndroidHeadlessUninstallCommandArguments : XHarnessCommandArguments { public TestPathArgument TestPath { get; } = new(); public RuntimePathArgument RuntimePath { get; } = new(); public DeviceIdArgument DeviceId { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { TestPath, RuntimePath, DeviceId, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/AndroidHeadless/Arguments/RuntimePathArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; internal class RuntimePathArgument : RequiredStringArgument { public RuntimePathArgument() : base("runtime-folder=|r=", "Path to the shared runtime") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/AndroidHeadless/Arguments/TestAssemblyArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; internal class TestAssemblyArgument : RequiredStringArgument { public TestAssemblyArgument() : base("test-assembly=|a=", "Name of the assembly to run") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/AndroidHeadless/Arguments/TestPathArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; internal class TestPathArgument : RequiredStringArgument { public TestPathArgument() : base("test-path=|p=", "Path to the test base folder") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/AndroidHeadless/Arguments/TestScriptArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; internal class TestScriptArgument : RequiredStringArgument { public TestScriptArgument() : base("test-script=|ts=", "Script to launch on-device") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/AndroidHeadless/IAndroidHeadlessAppRunArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; internal interface IAndroidHeadlessAppRunArguments { TestPathArgument TestPath { get; } RuntimePathArgument RuntimePath { get; } TestAssemblyArgument TestAssembly { get; } TestScriptArgument TestScript { get; } OutputDirectoryArgument OutputDirectory { get; } TimeoutArgument Timeout { get; } LaunchTimeoutArgument LaunchTimeout { get; } DeviceIdArgument DeviceId { get; } ApiVersionArgument ApiVersion { get; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleDeviceCommandsArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class AppleDeviceCommandArguments : XHarnessCommandArguments, IAppleArguments { public DeviceNameArgument DeviceName { get; } = new(); public IncludeWirelessArgument IncludeWireless { get; } = new(); public XcodeArgument XcodeRoot { get; } = new(); public MlaunchArgument MlaunchPath { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { DeviceName, IncludeWireless, XcodeRoot, MlaunchPath, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleInstallCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.iOS.Shared; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class AppleInstallCommandArguments : XHarnessCommandArguments, IAppleAppRunArguments { public AppPathArgument AppBundlePath { get; } = new(); public TargetArgument Target { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public XcodeArgument XcodeRoot { get; } = new(); public MlaunchArgument MlaunchPath { get; } = new(); public DeviceNameArgument DeviceName { get; } = new(); public IncludeWirelessArgument IncludeWireless { get; } = new(); public ResetSimulatorArgument ResetSimulator { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { AppBundlePath, Target, OutputDirectory, DeviceName, IncludeWireless, Timeout, XcodeRoot, MlaunchPath, ResetSimulator, }; public override void Validate() { base.Validate(); if (Target.Value.Platform == TestTarget.MacCatalyst) { throw new ArgumentException("This command is not supported with the maccatalyst target"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleJustRunCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.iOS.Shared; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class AppleJustRunCommandArguments : XHarnessCommandArguments, IAppleAppRunArguments { public BundleIdentifierArgument BundleIdentifier { get; } = new(); public TargetArgument Target { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public XcodeArgument XcodeRoot { get; } = new(); public MlaunchArgument MlaunchPath { get; } = new(); public DeviceNameArgument DeviceName { get; } = new(); public IncludeWirelessArgument IncludeWireless { get; } = new(); public EnableLldbArgument EnableLldb { get; } = new(); public EnvironmentalVariablesArgument EnvironmentalVariables { get; } = new(); public ExpectedExitCodeArgument ExpectedExitCode { get; } = new((int)ExitCode.SUCCESS); public SignalAppEndArgument SignalAppEnd { get; } = new(); public NoWaitArgument NoWait { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { BundleIdentifier, Target, OutputDirectory, DeviceName, IncludeWireless, Timeout, ExpectedExitCode, XcodeRoot, MlaunchPath, EnableLldb, SignalAppEnd, NoWait, EnvironmentalVariables, }; public override void Validate() { base.Validate(); if (Target.Value.Platform == TestTarget.MacCatalyst) { throw new ArgumentException("This command is not supported with the maccatalyst target"); } if (SignalAppEnd && NoWait) { throw new ArgumentException("--signal-app-end cannot be used in combination with --no-wait"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleJustTestCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.iOS.Shared; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class AppleJustTestCommandArguments : XHarnessCommandArguments, IAppleAppRunArguments { public BundleIdentifierArgument BundleIdentifier { get; } = new(); public TargetArgument Target { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public LaunchTimeoutArgument LaunchTimeout { get; set; } = new(TimeSpan.FromMinutes(5)); public XcodeArgument XcodeRoot { get; } = new(); public MlaunchArgument MlaunchPath { get; } = new(); public DeviceNameArgument DeviceName { get; } = new(); public IncludeWirelessArgument IncludeWireless { get; } = new(); public CommunicationChannelArgument CommunicationChannel { get; set; } = new(); public XmlResultJargonArgument XmlResultJargon { get; set; } = new(); public SingleMethodFilters SingleMethodFilters { get; } = new(); public ClassMethodFilters ClassMethodFilters { get; } = new(); public EnableLldbArgument EnableLldb { get; } = new(); public EnvironmentalVariablesArgument EnvironmentalVariables { get; } = new(); public SignalAppEndArgument SignalAppEnd { get; } = new(); public CommandArguments.EnableCoverageArgument EnableCoverage { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { BundleIdentifier, Target, OutputDirectory, Timeout, LaunchTimeout, XcodeRoot, MlaunchPath, DeviceName, IncludeWireless, CommunicationChannel, XmlResultJargon, SingleMethodFilters, ClassMethodFilters, EnableLldb, SignalAppEnd, EnvironmentalVariables, EnableCoverage, }; public override void Validate() { base.Validate(); if (Target.Value.Platform == TestTarget.MacCatalyst) { throw new ArgumentException("This command is not supported with the maccatalyst target"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleMlaunchCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class AppleMlaunchCommandArguments : XHarnessCommandArguments, IAppleArguments { public MlaunchArgument MlaunchPath { get; } = new(); public XcodeArgument XcodeRoot { get; } = new(); public TimeoutArgument Timeout { get; set; } = new(TimeSpan.FromMinutes(2)); public EnvironmentalVariablesArgument EnvironmentalVariables { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { MlaunchPath, XcodeRoot, Timeout, EnvironmentalVariables, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleResetSimulatorCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.iOS.Shared; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class AppleResetSimulatorCommandArguments : XHarnessCommandArguments, IAppleAppRunArguments { public TargetArgument Target { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(10)); public XcodeArgument XcodeRoot { get; } = new(); public MlaunchArgument MlaunchPath { get; } = new(); public DeviceNameArgument DeviceName { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { Target, DeviceName, Timeout, OutputDirectory, XcodeRoot, MlaunchPath, }; public override void Validate() { base.Validate(); if (Target.Value.Platform == TestTarget.MacCatalyst) { throw new ArgumentException("This command is not supported with the maccatalyst target"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleRunCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.Common.CLI; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class AppleRunCommandArguments : XHarnessCommandArguments, IAppleAppRunArguments { public AppPathArgument AppBundlePath { get; } = new(); public TargetArgument Target { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public LaunchTimeoutArgument LaunchTimeout { get; } = new(TimeSpan.FromMinutes(5)); public XcodeArgument XcodeRoot { get; } = new(); public MlaunchArgument MlaunchPath { get; } = new(); public DeviceNameArgument DeviceName { get; } = new(); public IncludeWirelessArgument IncludeWireless { get; } = new(); public EnableLldbArgument EnableLldb { get; } = new(); public EnvironmentalVariablesArgument EnvironmentalVariables { get; } = new(); public ResetSimulatorArgument ResetSimulator { get; } = new(); public ExpectedExitCodeArgument ExpectedExitCode { get; } = new((int)ExitCode.SUCCESS); public SignalAppEndArgument SignalAppEnd { get; } = new(); public NoWaitArgument NoWait { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { AppBundlePath, Target, OutputDirectory, DeviceName, IncludeWireless, Timeout, LaunchTimeout, ExpectedExitCode, XcodeRoot, MlaunchPath, EnableLldb, SignalAppEnd, NoWait, ResetSimulator, EnvironmentalVariables, }; public override void Validate() { base.Validate(); if (SignalAppEnd && NoWait) { throw new ArgumentException("--signal-app-end cannot be used in combination with --no-wait"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleStateCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class AppleStateCommandArguments : XHarnessCommandArguments { public XcodeArgument XcodeRoot { get; } = new(); public MlaunchArgument MlaunchPath { get; set; } = new(); public ShowSimulatorsUUIDArgument ShowSimulatorsUUID { get; set; } = new(); public ShowDevicesUUIDArgument ShowDevicesUUID { get; set; } = new(); public IncludeWirelessArgument IncludeWireless { get; } = new(); public UseJsonArgument UseJson { get; set; } = new(); protected override IEnumerable GetArguments() => new Argument[] { XcodeRoot, MlaunchPath, ShowSimulatorsUUID, ShowDevicesUUID, IncludeWireless, UseJson, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleTestCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class AppleTestCommandArguments : XHarnessCommandArguments, IAppleAppRunArguments { public AppPathArgument AppBundlePath { get; } = new(); public TargetArgument Target { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public LaunchTimeoutArgument LaunchTimeout { get; set; } = new(TimeSpan.FromMinutes(5)); public XcodeArgument XcodeRoot { get; } = new(); public MlaunchArgument MlaunchPath { get; } = new(); public DeviceNameArgument DeviceName { get; } = new(); public IncludeWirelessArgument IncludeWireless { get; } = new(); public CommunicationChannelArgument CommunicationChannel { get; set; } = new(); public XmlResultJargonArgument XmlResultJargon { get; set; } = new(); public SingleMethodFilters SingleMethodFilters { get; } = new(); public ClassMethodFilters ClassMethodFilters { get; } = new(); public EnableLldbArgument EnableLldb { get; } = new(); public EnvironmentalVariablesArgument EnvironmentalVariables { get; } = new(); public ResetSimulatorArgument ResetSimulator { get; } = new(); public SignalAppEndArgument SignalAppEnd { get; } = new(); public CommandArguments.EnableCoverageArgument EnableCoverage { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { AppBundlePath, Target, OutputDirectory, Timeout, LaunchTimeout, XcodeRoot, MlaunchPath, DeviceName, IncludeWireless, CommunicationChannel, XmlResultJargon, SingleMethodFilters, ClassMethodFilters, EnableLldb, SignalAppEnd, EnvironmentalVariables, ResetSimulator, EnableCoverage, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleUninstallCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.iOS.Shared; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class AppleUninstallCommandArguments : XHarnessCommandArguments, IAppleAppRunArguments { public BundleIdentifierArgument BundleIdentifier { get; } = new(); public TargetArgument Target { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(10)); public XcodeArgument XcodeRoot { get; } = new(); public MlaunchArgument MlaunchPath { get; } = new(); public DeviceNameArgument DeviceName { get; } = new(); public IncludeWirelessArgument IncludeWireless { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { BundleIdentifier, Target, OutputDirectory, Timeout, XcodeRoot, MlaunchPath, DeviceName, IncludeWireless, }; public override void Validate() { base.Validate(); if (Target.Value.Platform == TestTarget.MacCatalyst) { throw new ArgumentException("This command is not supported with the maccatalyst target"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/BundleIdentifierArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class BundleIdentifierArgument : RequiredStringArgument { public BundleIdentifierArgument() : base("app|a=", "Bundle identifier of the installed application") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/CommunicationChannelArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Apple; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// The way the simulator/device talks back to XHarness. /// internal class CommunicationChannelArgument : Argument { public CommunicationChannelArgument() : base("communication-channel=", "The communication channel to use to communicate with the device. Default set to USB tunnel", CommunicationChannel.UsbTunnel) { } public override void Action(string argumentValue) { Value = ParseArgument("communication-channel", argumentValue); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/DeviceNameArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// Name of a specific device we want to target. /// internal class DeviceNameArgument : StringArgument { public DeviceNameArgument() : base("device=", "Name or UDID of a simulator/device you wish to target") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/EnableLldbArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// Enable the lldb debugger to be used with the launched application. /// internal class EnableLldbArgument : SwitchArgument { public EnableLldbArgument() : base("enable-lldb", "Allow to debug the launched application using lldb", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/EnvironmentalVariablesArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// Environmental variables set when executing the application. /// internal class EnvironmentalVariablesArgument : Argument { public IReadOnlyCollection<(string, string?)> Value => _environmentalVariables; private readonly List<(string, string?)> _environmentalVariables = new(); public EnvironmentalVariablesArgument() : base("set-env=", "Environmental variable to set for the application in format key=value. Can be used multiple times") { } public override void Action(string argumentValue) { var position = argumentValue.IndexOf('='); if (position == -1) { throw new ArgumentException($"The set-env argument {argumentValue} must be in the key=value format"); } var key = argumentValue.Substring(0, position); var value = argumentValue.Substring(position + 1); _environmentalVariables.Add((key, value)); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/ForceInstallationArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class ForceInstallationArgument : SwitchArgument { public ForceInstallationArgument() : base("force", "Install again even if already installed", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/HideProgressArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class HideProgressArgument : SwitchArgument { public HideProgressArgument() : base("hide-progress", "Won't show progress when downloading the Simulator runtime", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/IncludeWirelessArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// If enabled, takes longer to list devices and looks for wirelessly connected ones too. /// internal class IncludeWirelessArgument : SwitchArgument { public IncludeWirelessArgument() : base("wireless:|include-wireless-devices:", "Also look for wirelessly connected devices (takes longer)", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/LaunchTimeoutArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// How long we wait before app starts and first test should start running /// internal class LaunchTimeoutArgument : TimeSpanArgument { public LaunchTimeoutArgument(TimeSpan defaultValue) : base("launch-timeout=|lt=", "Time span in the form of \"00:00:00\" or number of seconds to wait for the app to start", defaultValue) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/ListInstalledArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class ListInstalledArgument : SwitchArgument { public ListInstalledArgument() : base("installed", "Lists installed simulators only", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/MlaunchArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// Path to the mlaunch binary. /// Default comes from the NuGet. /// internal class MlaunchArgument : Argument { public MlaunchArgument() : base("mlaunch=", "Path to the mlaunch binary. Defaults to mlaunch bundled with the XHarness nupkg", MacOSProcessManager.DetectMlaunchPath()) { } public override void Action(string argumentValue) => Value = RootPath(argumentValue); public override void Validate() { if (!File.Exists(Value)) { throw new ArgumentException( $"Failed to find mlaunch at {Value}. " + $"Make sure you specify --mlaunch or set the {EnvironmentVariables.Names.MLAUNCH_PATH} env var. " + $"See README.md for more information"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/NoWaitArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class NoWaitArgument : SwitchArgument { public NoWaitArgument() : base("no-wait|nowait", "Don't wait for the app to shut down", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/ResetSimulatorArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// Kills running simulator processes and removes any previous data before running. /// internal class ResetSimulatorArgument : SwitchArgument { public ResetSimulatorArgument() : base("reset-simulator", "Shuts down the simulator and clears all data before running. Shuts it down after the run too", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/ShowDevicesUUIDArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class ShowDevicesUUIDArgument : SwitchArgument { public ShowDevicesUUIDArgument() : base("include-devices-uuid", "Include the devices UUID. Defaults to true", true) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/ShowSimulatorsUUIDArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal class ShowSimulatorsUUIDArgument : SwitchArgument { public ShowSimulatorsUUIDArgument() : base("include-simulator-uuid", "Include the simulators UUID. Defaults to false", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/SignalAppEndArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// Enables extra signaling between the TestRunner application and XHarness to work around problems in newer iOS. /// internal class SignalAppEndArgument : SwitchArgument { public SignalAppEndArgument() : base("signal-app-end", "Tells the test application to signal back when tests have finished (some iOS/tvOS cannot detect this reliably otherwise)", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/TargetArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// Test target (device, simulator, OS version...). /// internal class TargetArgument : Argument { public TargetArgument() : base("target=|targets=|t=", "Test target (device/simulator and OS)", TestTargetOs.None) { } public override void Action(string argumentValue) { try { Value = argumentValue.ParseAsAppRunnerTargetOs(); } catch (ArgumentOutOfRangeException e) { throw new ArgumentException( $"Failed to parse test target '{argumentValue}'. Available targets are:" + GetAllowedValues(t => t.AsString(), invalidValues: TestTarget.None) + Environment.NewLine + Environment.NewLine + "You can also specify desired OS version, e.g. ios-simulator-64_13.4", e); } } public override void Validate() { if (Value == TestTargetOs.None) { throw new ArgumentException("No test target specified"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/XcodeArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// Path to where Xcode is located. /// internal class XcodeArgument : PathArgument { public XcodeArgument() : base("xcode=", "Path where Xcode is installed. If not provided, determined from xcode-select", false) { } public override void Validate() { if (Value != null && !Directory.Exists(Value)) { throw new ArgumentException($"Failed to find Xcode root at {Value}"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/XmlResultJargonArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Common; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// /// Allows to specify the xml format to be used in the result files. /// internal class XmlResultJargonArgument : Argument { public XmlResultJargonArgument() : base("xml-jargon=|xj=", $"The xml format to be used in the unit test results. Can be {XmlResultJargon.TouchUnit}, {XmlResultJargon.NUnitV2}, {XmlResultJargon.NUnitV3} or {XmlResultJargon.xUnit}. Default is xUnit", XmlResultJargon.xUnit) { } public override void Action(string argumentValue) { Value = ParseArgument("xml-jargon", argumentValue, invalidValues: XmlResultJargon.Missing); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/IAppleAppRunArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal interface IAppleAppRunArguments : IAppleArguments { TargetArgument Target { get; } OutputDirectoryArgument OutputDirectory { get; } TimeoutArgument Timeout { get; } DeviceNameArgument DeviceName { get; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/IAppleArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; internal interface IAppleArguments : IXHarnessCommandArguments { XcodeArgument XcodeRoot { get; } MlaunchArgument MlaunchPath { get; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Simulators/FindCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Linq; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple.Simulators; internal class FindCommandArguments : SimulatorsCommandArguments { protected override IEnumerable GetAdditionalArguments() => Enumerable.Empty(); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Simulators/InstallCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple.Simulators; internal class InstallCommandArguments : SimulatorsCommandArguments { public ForceInstallationArgument Force { get; } = new(); public HideProgressArgument HideProgress { get; } = new(); protected override IEnumerable GetAdditionalArguments() => new Argument[] { Force, HideProgress, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Simulators/ListCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple.Simulators; internal class ListCommandArguments : SimulatorsCommandArguments { public ListInstalledArgument ListInstalledOnly { get; } = new(); protected override IEnumerable GetAdditionalArguments() => new Argument[] { ListInstalledOnly, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Simulators/SimulatorsCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Linq; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple.Simulators; internal abstract class SimulatorsCommandArguments : XHarnessCommandArguments { public XcodeArgument XcodeRoot { get; } = new(); protected sealed override IEnumerable GetArguments() => GetAdditionalArguments().Concat(new Argument[] { XcodeRoot }); protected abstract IEnumerable GetAdditionalArguments(); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Argument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using OpenQA.Selenium; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; public abstract class Argument { public string Prototype { get; } public string Description { get; } protected Argument(string prototype, string description) { Prototype = prototype; Description = description; } /// /// Action invoked when argument is found. /// public abstract void Action(string argumentValue); /// /// Allows to implement additional validation (e.g. "directory exists?"). /// Should throw an ArgumentException if validation fails. /// public virtual void Validate() { } protected string RootPath(string path) { if (!Path.IsPathRooted(path)) { path = Path.Combine(Directory.GetCurrentDirectory(), path); } return path; } /// /// Helper method that returns a bullet list of available enum values ready for printing to console. /// /// Enum type /// How to print each enum value. Default is ToString() /// List of values that should not be available to set and are not listed then /// Print-ready list of allowed values protected static string GetAllowedValues(Func? display = null, params TEnum[]? invalidValues) where TEnum : struct, IConvertible { var values = Enum.GetValues(typeof(TEnum)).Cast(); if (invalidValues != null) { values = values.Where(v => !invalidValues.Contains(v)); } var separator = Environment.NewLine + "\t- "; IEnumerable allowedValued = values.Select(t => display?.Invoke(t) ?? t.ToString()); return separator + string.Join(separator, allowedValued); } /// /// Helper method that enables parsing of enums from string. /// When an invalid value is supplied, available values are printed. /// /// Enum type /// Name of the argument that is being parsed, strictly for help printing purposes /// Value of the arg to be parsed /// List of values that should not be available to set /// Parsed enum value protected static TEnum ParseArgument(string argumentName, string? value, params TEnum[]? invalidValues) where TEnum : struct, IConvertible { if (value == null) { throw new ArgumentNullException(message: $"Empty value supplied to {argumentName}", null); } if (value.All(c => char.IsDigit(c))) { // Any int would parse into enum successfully, so we forbid that throw new ArgumentException( $"Invalid value '{value}' supplied for {argumentName}. " + $"Valid values are:" + GetAllowedValues(invalidValues: invalidValues)); } var type = typeof(TEnum); if (!type.IsEnum) { throw new ArgumentException(nameof(TEnum) + " must be an enumerated type"); } if (Enum.TryParse(value, ignoreCase: true, out TEnum result)) { if (invalidValues != null && invalidValues.Contains(result)) { throw new ArgumentException($"{result} is an invalid value for {argumentName}"); } return result; } IEnumerable validOptions = Enum.GetValues(type).Cast(); if (invalidValues != null) { validOptions = validOptions.Where(v => !invalidValues.Contains(v)); } throw new ArgumentException( $"Invalid value '{value}' supplied in {argumentName}. " + $"Valid values are:" + GetAllowedValues(invalidValues: invalidValues)); } } public abstract class Argument : Argument { public virtual T Value { get; protected set; } protected Argument(string prototype, string description, T defaultValue) : base(prototype, description) { Value = defaultValue; } public static implicit operator T(Argument arg) => arg.Value; public override string ToString() => Value?.ToString() ?? base.ToString()!; } public abstract class IntArgument : Argument { public IntArgument(string prototype, string description, int defaultValue = 0) : base(prototype, description, defaultValue) { } public override void Action(string argumentValue) { if (int.TryParse(argumentValue, out var number)) { Value = number; return; } throw new ArgumentException($"{Prototype} must be an integer"); } } public abstract class OptionalIntArgument : Argument { public OptionalIntArgument(string prototype, string description) : base(prototype, description, null) { } public override void Action(string argumentValue) { if (int.TryParse(argumentValue, out var number)) { Value = number; return; } throw new ArgumentException($"{Prototype} must be an integer"); } } public abstract class StringArgument : Argument { public StringArgument(string prototype, string description) : base(prototype, description, null) { } public override void Action(string argumentValue) => Value = argumentValue; } public abstract class RequiredStringArgument : Argument { public RequiredStringArgument(string prototype, string description, string defaultValue = null!) : base(prototype, description, defaultValue) { } public override void Action(string argumentValue) => Value = argumentValue; public override void Validate() { if (string.IsNullOrEmpty(Value)) { throw new ArgumentException($"Required argument {Prototype} was not supplied"); } } } public abstract class TimeSpanArgument : Argument { protected TimeSpanArgument(string prototype, string description, TimeSpan defaultValue) : base(prototype, description, defaultValue) { } public override void Action(string argumentValue) { if (int.TryParse(argumentValue, out var timeout)) { Value = TimeSpan.FromSeconds(timeout); return; } if (TimeSpan.TryParse(argumentValue, out var timespan)) { Value = timespan; return; } throw new ArgumentException($"{Prototype} must be an integer - a number of seconds, or a timespan (00:30:00)"); } } public abstract class PathArgument : StringArgument { private readonly bool _isRequired; protected PathArgument(string prototype, string description, bool isRequired) : base(prototype, description) { _isRequired = isRequired; } public override void Action(string argumentValue) => Value = RootPath(argumentValue); public override void Validate() { if (_isRequired && string.IsNullOrEmpty(Value)) { throw new ArgumentException($"Required argument {Prototype} was not supplied"); } } } public abstract class RequiredPathArgument : RequiredStringArgument { protected RequiredPathArgument(string prototype, string description) : base(prototype, description) { } public override void Action(string argumentValue) => Value = RootPath(argumentValue); } public abstract class SwitchArgument : Argument { private readonly bool _defaultValue; public SwitchArgument(string prototype, string description, bool defaultValue) : base(prototype, description, defaultValue) { _defaultValue = defaultValue; } public override void Action(string argumentValue) { if (string.IsNullOrEmpty(argumentValue)) { Value = !_defaultValue; } else { Value = !argumentValue.Equals("false", StringComparison.InvariantCultureIgnoreCase) && !argumentValue.Equals("no", StringComparison.InvariantCultureIgnoreCase) && !argumentValue.Equals("off", StringComparison.InvariantCultureIgnoreCase) && !argumentValue.Equals("0", StringComparison.InvariantCultureIgnoreCase); } } public override string ToString() => Value ? "true" : "false"; } public abstract class EnumPageLoadStrategyArgument : Argument { private readonly PageLoadStrategy _defaultValue; public EnumPageLoadStrategyArgument(string prototype, string description, PageLoadStrategy defaultValue) : base(prototype, description, defaultValue) { _defaultValue = defaultValue; } public override void Action(string argumentValue) { if (string.IsNullOrEmpty(argumentValue)) { Value = _defaultValue; } else { Value = argumentValue.Equals("none", StringComparison.OrdinalIgnoreCase) ? PageLoadStrategy.None : argumentValue.Equals("eager", StringComparison.OrdinalIgnoreCase) ? PageLoadStrategy.Eager : argumentValue.Equals("normal", StringComparison.OrdinalIgnoreCase) ? PageLoadStrategy.Normal : _defaultValue; } } } public abstract class RepeatableArgument : Argument> { private readonly List _values = new(); protected RepeatableArgument(string prototype, string description) : base(prototype, description + ". Can be used more than once", Array.Empty()) { } public override IEnumerable Value => _values; public override void Action(string argumentValue) => _values.Add(argumentValue); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/AppPathArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; /// /// Path to the app bundle. /// internal class AppPathArgument : RequiredPathArgument { public AppPathArgument() : base("app|a=", "Path to an already-packaged app") { } } /// /// Path to the app bundle. /// internal class OptionalAppPathArgument : PathArgument { public OptionalAppPathArgument() : base("app|a=", "Path to an already-packaged app", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/ClassMethodFilters.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; /// /// Tests classes to be included in the run while all others are ignored. /// internal class ClassMethodFilters : RepeatableArgument { public ClassMethodFilters() : base("class|c=", "Test class to be ran in the test application. When this parameter is used only the " + "tests that have been provided by the '--method' and '--class' arguments will be ran. All other test will be ignored") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/EnableCoverageArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; /// /// Shared --enable-coverage switch used across all platforms (Android, Apple, WASM). /// internal class EnableCoverageArgument : SwitchArgument { public EnableCoverageArgument() : base("enable-coverage", "Enable code coverage collection during test execution", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/ExpectedExitCodeArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; /// /// Exit code returned by the instrumentation for a successful run. /// internal class ExpectedExitCodeArgument : IntArgument { public ExpectedExitCodeArgument(int defaultValue) : base("expected-exit-code=", $"If specified, sets the expected exit code for a successful run. Default set to {defaultValue}", defaultValue) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/OutputDirectoryArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; /// /// Path where the outputs of execution will be stored /// internal class OutputDirectoryArgument : RequiredPathArgument { public OutputDirectoryArgument() : base("output-directory=|o=", "Directory where logs and results will be saved") { } public override void Validate() { if (!Directory.Exists(Value ?? throw new ArgumentNullException("You must provide an output directory where results will be stored"))) { Directory.CreateDirectory(Value); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/SingleMethodFilters.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; /// /// Methods to be included in the test run while all others are ignored. /// internal class SingleMethodFilters : RepeatableArgument { public SingleMethodFilters() : base("method|m=", "Method to be ran in the test application. When this parameter is used only the " + "tests that have been provided by the '--method' and '--class' arguments will be ran. All other test will be ignored") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/TimeoutArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; /// /// How long XHarness should wait until a test execution completes before clean up (kill running apps, uninstall, etc) /// internal class TimeoutArgument : TimeSpanArgument { public TimeoutArgument(TimeSpan defaultTimeout) : base("timeout=", "Time span in the form of \"00:00:00\" or number of seconds to wait for the run to complete", defaultTimeout) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/TypeFromAssemblyArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Loader; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; internal class TypeFromAssemblyArgument : Argument> where T : class { private readonly bool _repeatable; public TypeFromAssemblyArgument(string prototype, string description, bool repeatable) : base(prototype, description, new List<(string path, string? type)>()) { _repeatable = repeatable; } public IEnumerable GetLoadedTypes() { foreach ((string assemblyPath, string? typeName) in Value) { var extensionAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(assemblyPath)); var loadedType = extensionAssembly?.GetTypes().Where(type => type.FullName == typeName).FirstOrDefault(); if (loadedType is null) throw new Exception($"Can't find type named {typeName} in {assemblyPath}"); yield return loadedType; } } public override void Action(string argumentValue) { var split = argumentValue.Split(',', 2); var file = split[0]; string? type = split.Length > 1 ? split[1] : null; Value.Add((file, type)); } public override void Validate() { base.Validate(); if (!_repeatable && Value.Count > 1) throw new ArgumentException($"{Prototype} can only be passed once"); foreach (var (path, type) in Value) { if (string.IsNullOrWhiteSpace(path)) { throw new ArgumentException($"Empty path to assembly"); } if (!File.Exists(path)) { throw new ArgumentException($"Failed to find the assembly at {path}"); } if (string.IsNullOrEmpty(type)) { throw new ArgumentException($"No type name given with assembly {path}"); } } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerHttpEnvironmentVariables.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; internal class WebServerHttpEnvironmentVariables : Argument> { public WebServerHttpEnvironmentVariables() : base( "set-web-server-http-env=", "Comma separated list of environment variable names, which should be set to HTTP host and port, for the unit test, which use xharness as test web server", Array.Empty()) { } public override void Action(string argumentValue) => Value = argumentValue.Split(','); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerHttpsEnvironmentVariables.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; internal class WebServerHttpsEnvironmentVariables : Argument> { public WebServerHttpsEnvironmentVariables() : base( "set-web-server-https-env=", "Comma separated list of environment variable names, which should be set to HTTPS host and port, for the unit test, which use xharness as test web server", Array.Empty()) { } public override void Action(string argumentValue) => Value = argumentValue.Split(','); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerMiddlewareArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; internal class WebServerMiddlewareArgument : TypeFromAssemblyArgument { public WebServerMiddlewareArgument() : base( "web-server-middleware=", ", to assembly and type which contains Kestrel middleware for local test server. Could be used multiple times to load multiple middlewares", repeatable: true) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerUploadResults.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; internal class WebServerUploadResults : SwitchArgument { public WebServerUploadResults() : base("web-server-upload-results", "Enable uploading XML test results", true) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerUseCorsArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; internal class WebServerUseCorsArguments : SwitchArgument { public WebServerUseCorsArguments() : base("web-server-use-cors", "Enable any CORS headers", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerUseCrossOriginPolicyArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; internal class WebServerUseCrossOriginPolicyArguments : SwitchArgument { public WebServerUseCrossOriginPolicyArguments() : base("web-server-use-cop", "Sets Cross-Origin-Opener-Policy=same-origin and Cross-Origin-Embedder-Policy=require-corp", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerUseDefaultFiles.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; internal class WebServerUseDefaultFilesArguments : SwitchArgument { public WebServerUseDefaultFilesArguments() : base("web-server-use-default-files", "Enable default files like index.html", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerUseHttpsArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; internal class WebServerUseHttpsArguments : SwitchArgument { public WebServerUseHttpsArguments() : base("web-server-use-https", "Bind HTTPS port too", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/DiagnosticsArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; public class DiagnosticsArgument : StringArgument { public DiagnosticsArgument() : base("diagnostics=", "Path to a file where diagnostics data from the run will be stored") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/HelpArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; public class HelpArgument : SwitchArgument { public HelpArgument() : base("help|h", string.Empty, false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/UseJsonArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; internal class UseJsonArgument : SwitchArgument { public UseJsonArgument() : base("json", "Use json as the output format", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/VerbosityArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; public class VerbosityArgument : Argument { public LogLevel Value { get; private set; } = LogLevel.Information; public VerbosityArgument(LogLevel level) : base("verbosity:|v:", "Verbosity level - defaults to 'Information' if not specified. If passed without value, 'Debug' is assumed (highest)") { Value = level; } public override void Action(string argumentValue) { Value = string.IsNullOrEmpty(argumentValue) ? LogLevel.Debug : ParseArgument("verbosity", argumentValue); } public static implicit operator LogLevel(VerbosityArgument arg) => arg.Value; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASI/Arguments/WasmEngineArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasi; internal class WasmEngineArgument : Argument { public WasmEngineArgument() : base("engine=|e=", "Specifies the Wasm engine to be used", null) { } public override void Action(string argumentValue) => Value = ParseArgument("engine", argumentValue); // Set WasmTime as default engine public override void Validate() => Value ??= WasmEngine.WasmTime; } /// /// Specifies a name of a wasm engine used to run WASI application. /// internal enum WasmEngine { /// /// WasmTime /// WasmTime, } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASI/Arguments/WasmEngineArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasi; internal class WasmEngineArguments : RepeatableArgument { public WasmEngineArguments() : base("engine-arg=", "Argument to pass to the wasm engine") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASI/Arguments/WasmEngineLocationArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasi; internal class WasmEngineLocationArgument : StringArgument { public WasmEngineLocationArgument() : base("wasm-engine-path=", "Path to the wasm engine to be used. This must correspond to the engine specified with -e") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASI/WasiTestCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasi; internal class WasiTestCommandArguments : XHarnessCommandArguments, IWebServerArguments { public WasmEngineArgument Engine { get; } = new(); public WasmEngineLocationArgument EnginePath { get; } = new(); public WasmEngineArguments EngineArgs { get; } = new(); public ExpectedExitCodeArgument ExpectedExitCode { get; } = new((int)Common.CLI.ExitCode.SUCCESS); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public WebServerMiddlewareArgument WebServerMiddlewarePathsAndTypes { get; } = new(); public WebServerHttpEnvironmentVariables WebServerHttpEnvironmentVariables { get; } = new(); public WebServerHttpsEnvironmentVariables WebServerHttpsEnvironmentVariables { get; } = new(); public WebServerUseHttpsArguments WebServerUseHttps { get; } = new(); public WebServerUseCorsArguments WebServerUseCors { get; } = new(); public WebServerUseCrossOriginPolicyArguments WebServerUseCrossOriginPolicy { get; } = new(); public WebServerUseDefaultFilesArguments WebServerUseDefaultFiles { get; } = new(); public bool IsWebServerEnabled => WebServerMiddlewarePathsAndTypes.Value.Count > 0; public WebServerUploadResults WebServerUploadResults { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { Engine, EnginePath, EngineArgs, OutputDirectory, Timeout, ExpectedExitCode, WebServerMiddlewarePathsAndTypes, WebServerHttpEnvironmentVariables, WebServerHttpsEnvironmentVariables, WebServerUseHttps, WebServerUseCors, WebServerUseCrossOriginPolicy, WebServerUseDefaultFiles, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/BackgroundThrottlingArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class BackgroundThrottlingArgument : SwitchArgument { public BackgroundThrottlingArgument() : base("background-throttling", "Hide browser tab and don't prevent timer throttling", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/BrowserArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Runtime.InteropServices; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class BrowserArgument : Argument { public BrowserArgument() : base("browser=|b=", "Specifies the browser to be used. Default is Chrome", Browser.Chrome) { } public override void Action(string argumentValue) => Value = ParseArgument("browser", argumentValue); public override void Validate() { base.Validate(); if (Value == Browser.Safari && !RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { throw new ArgumentException("Safari is only supported on OSX"); } } } /// /// Specifies the name of a Browser used to run the WASM application /// internal enum Browser { /// /// Chrome /// Chrome, /// /// Safari /// Safari, /// /// Firefox /// Firefox, /// /// Edge /// Edge } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/BrowserArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class BrowserArguments : RepeatableArgument { public BrowserArguments() : base("browser-arg=", "Argument to pass to the browser") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/BrowserLocationArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class BrowserLocationArgument : StringArgument { public BrowserLocationArgument() : base("browser-path=", "Path to the browser to be used. This must correspond to the browser specified with -b") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/DebuggerPortArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class DebuggerPortArgument : Argument { public DebuggerPortArgument() : base("debugger=|d=", "Run browser in debug mode, with a port to listen on. Default port number is 9222", null) { } public override void Action(string argumentValue) { if (int.TryParse(argumentValue, out var number)) { Value = number; return; } throw new ArgumentException($"{Prototype} must be an integer"); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/ErrorPatternsFileArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class ErrorPatternsFileArgument : StringArgument { public ErrorPatternsFileArgument() : base("error-patterns=|p=", "File containing error patterns. Each line prefixed with '@', or '%' for a simple string, or a .net regex, respectively") { } public override void Validate() { base.Validate(); if (Value != null && !File.Exists(Value)) { throw new ArgumentException($"Cannot find error patterns file {Value}"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/HTMLFileArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class HTMLFileArgument : RequiredStringArgument { public HTMLFileArgument(string defaultValue) : base("html-file=", $"Main html file to load from the app directory. Default is {defaultValue}", defaultValue) { } public override void Validate() { base.Validate(); if (Path.IsPathRooted(Value)) { throw new ArgumentException("--html-file argument must be a relative path"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/JavaScriptEngineArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class JavaScriptEngineArgument : Argument { public JavaScriptEngineArgument() : base("engine=|e=", "Specifies the JavaScript engine to be used", null) { } public override void Action(string argumentValue) => Value = ParseArgument("engine", argumentValue); public override void Validate() { if (Value == null) { throw new ArgumentException("Engine not specified"); } } } /// /// Specifies a name of a JavaScript engine used to run WASM application. /// internal enum JavaScriptEngine { /// /// V8 /// V8, /// /// JavaScriptCore /// JavaScriptCore, /// /// SpiderMonkey /// SpiderMonkey, /// /// NodeJS /// NodeJS, } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/JavaScriptEngineArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class JavaScriptEngineArguments : RepeatableArgument { public JavaScriptEngineArguments() : base("engine-arg=", "Argument to pass to the JavaScript engine") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/JavaScriptEngineLocationArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class JavaScriptEngineLocationArgument : StringArgument { public JavaScriptEngineLocationArgument() : base("js-engine-path=", "Path to the JS engine to be used. This must correspond to the engine specified with -e") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/JavaScriptFileArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class JavaScriptFileArgument : RequiredStringArgument { public JavaScriptFileArgument(string defaultValue) : base("js-file=", "Main JavaScript file to be run on the JavaScript engine. Default is " + defaultValue, defaultValue) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/LocaleArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; using System; internal class LocaleArgument : RequiredStringArgument { public LocaleArgument(string defaultValue) : base("locale=", $"Sets the browser's/node's language through an environment variable, default value {defaultValue}", defaultValue) {} } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/NoHeadlessArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class NoHeadlessArgument : SwitchArgument { public NoHeadlessArgument() : base("no-headless", "Don't run in headless mode", false) { } public void Set(bool value) => Value = value; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/NoIncognitoArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class NoIncognitoArgument : SwitchArgument { public NoIncognitoArgument() : base("no-incognito", "Don't run in incognito mode", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/NoQuitArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class NoQuitArgument : SwitchArgument { public NoQuitArgument() : base("no-quit", "Don't quit the xharness process after the tests are done running. Implies --no-headless", false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/PageLoadStrategyArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; using OpenQA.Selenium; using System; internal class PageLoadStrategyArgument : EnumPageLoadStrategyArgument { private const string HelpMessage = $@"Decides how long WebDriver will hold off on completing a navigation method. NORMAL (default): Does not block WebDriver at all. Ready state: complete. EAGER: DOM access is ready, but other resources like images may still be loading. Ready state: interactive. NONE: Does not block WebDriver at all. Ready state: any."; public PageLoadStrategyArgument(PageLoadStrategy defaultValue) : base("pageLoadStrategy=", HelpMessage, defaultValue) {} } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/SymbolMapFileArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class SymbolMapFileArgument : StringArgument { public SymbolMapFileArgument() : base("symbol-map=|s=", "Path to wasm symbol map file generated by emscripten") { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/SymbolicatePatternsFileArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class SymbolicatePatternsFileArgument : StringArgument { private const string HelpMessage = @"File containing .net regex patterns for replacing wasm-function numbers with the corresponding names." + @" The regex must contain a group named `funcNum` for getting the function number." + @" And an optional group named `replaceSection` for matching the part of the string to be replaced by the name."; public SymbolicatePatternsFileArgument() : base("symbol-patterns=", HelpMessage) { } public override void Validate() { base.Validate(); if (Value != null && !File.Exists(Value)) { throw new ArgumentException($"Cannot find error patterns file {Value}"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/Arguments/SymbolicatorArgument.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Common; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class SymbolicatorArgument : TypeFromAssemblyArgument { public SymbolicatorArgument() : base( "symbolicator=", $", to assembly, and type which contains the wasm symbolicator", repeatable: false) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/IWebServerArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal interface IWebServerArguments { bool IsWebServerEnabled { get; } WebServerMiddlewareArgument WebServerMiddlewarePathsAndTypes { get; } WebServerHttpEnvironmentVariables WebServerHttpEnvironmentVariables { get; } WebServerHttpsEnvironmentVariables WebServerHttpsEnvironmentVariables { get; } WebServerUseHttpsArguments WebServerUseHttps { get; } WebServerUseCorsArguments WebServerUseCors { get; } WebServerUseCrossOriginPolicyArguments WebServerUseCrossOriginPolicy { get; } WebServerUseDefaultFilesArguments WebServerUseDefaultFiles { get; } WebServerUploadResults WebServerUploadResults { get; } OutputDirectoryArgument OutputDirectory { get; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using Microsoft.DotNet.XHarness.Common.CLI; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class WasmTestBrowserCommandArguments : XHarnessCommandArguments, IWebServerArguments { public AppPathArgument AppPackagePath { get; } = new(); public BrowserArgument Browser { get; } = new(); public BrowserLocationArgument BrowserLocation { get; } = new(); public BrowserArguments BrowserArgs { get; } = new(); public HTMLFileArgument HTMLFile { get; } = new("index.html"); public ErrorPatternsFileArgument ErrorPatternsFile { get; } = new(); public ExpectedExitCodeArgument ExpectedExitCode { get; } = new((int)ExitCode.SUCCESS); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public DebuggerPortArgument DebuggerPort { get; set; } = new(); public NoIncognitoArgument NoIncognito { get; } = new(); public NoHeadlessArgument NoHeadless { get; } = new(); public NoQuitArgument NoQuit { get; } = new(); public BackgroundThrottlingArgument BackgroundThrottling { get; } = new(); public LocaleArgument Locale { get; } = new("en-US"); public PageLoadStrategyArgument PageLoadStrategy { get; } = new(OpenQA.Selenium.PageLoadStrategy.Normal); public SymbolMapFileArgument SymbolMapFileArgument { get; } = new(); public SymbolicatePatternsFileArgument SymbolicatePatternsFileArgument { get; } = new(); public SymbolicatorArgument SymbolicatorArgument { get; } = new(); public WebServerMiddlewareArgument WebServerMiddlewarePathsAndTypes { get; } = new(); public WebServerHttpEnvironmentVariables WebServerHttpEnvironmentVariables { get; } = new(); public WebServerHttpsEnvironmentVariables WebServerHttpsEnvironmentVariables { get; } = new(); public WebServerUseHttpsArguments WebServerUseHttps { get; } = new(); public WebServerUseCorsArguments WebServerUseCors { get; } = new(); public WebServerUseCrossOriginPolicyArguments WebServerUseCrossOriginPolicy { get; } = new(); public WebServerUseDefaultFilesArguments WebServerUseDefaultFiles { get; } = new(); public WebServerUploadResults WebServerUploadResults { get; } = new(); public bool IsWebServerEnabled => WebServerMiddlewarePathsAndTypes.Value.Count > 0; public CommandArguments.EnableCoverageArgument EnableCoverage { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { AppPackagePath, Browser, BrowserLocation, BrowserArgs, HTMLFile, ErrorPatternsFile, ExpectedExitCode, OutputDirectory, Timeout, DebuggerPort, NoIncognito, NoHeadless, NoQuit, BackgroundThrottling, Locale, PageLoadStrategy, SymbolMapFileArgument, SymbolicatePatternsFileArgument, SymbolicatorArgument, WebServerMiddlewarePathsAndTypes, WebServerHttpEnvironmentVariables, WebServerHttpsEnvironmentVariables, WebServerUseHttps, WebServerUseCors, WebServerUseCrossOriginPolicy, WebServerUseDefaultFiles, EnableCoverage, }; public override void Validate() { base.Validate(); if (!string.IsNullOrEmpty(BrowserLocation)) { if (Browser == Wasm.Browser.Safari) { throw new ArgumentException("Safari driver doesn't support custom browser path"); } if (!File.Exists(BrowserLocation)) { throw new ArgumentException($"Could not find browser at {BrowserLocation}"); } } if (DebuggerPort.Value != null || NoQuit) { NoHeadless.Set(true); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class WasmTestCommandArguments : XHarnessCommandArguments, IWebServerArguments { public JavaScriptEngineArgument Engine { get; } = new(); public JavaScriptEngineLocationArgument EnginePath { get; } = new(); public JavaScriptEngineArguments EngineArgs { get; } = new(); public JavaScriptFileArgument JSFile { get; } = new("runtime.js"); public ErrorPatternsFileArgument ErrorPatternsFile { get; } = new(); public ExpectedExitCodeArgument ExpectedExitCode { get; } = new((int)Common.CLI.ExitCode.SUCCESS); public OutputDirectoryArgument OutputDirectory { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); public LocaleArgument Locale { get; } = new("en-US"); public PageLoadStrategyArgument PageLoadStrategy { get; } = new(OpenQA.Selenium.PageLoadStrategy.Normal); public SymbolMapFileArgument SymbolMapFileArgument { get; } = new(); public SymbolicatePatternsFileArgument SymbolicatePatternsFileArgument { get; } = new(); public SymbolicatorArgument SymbolicatorArgument { get; } = new(); public WebServerMiddlewareArgument WebServerMiddlewarePathsAndTypes { get; } = new(); public WebServerHttpEnvironmentVariables WebServerHttpEnvironmentVariables { get; } = new(); public WebServerHttpsEnvironmentVariables WebServerHttpsEnvironmentVariables { get; } = new(); public WebServerUseHttpsArguments WebServerUseHttps { get; } = new(); public WebServerUseCorsArguments WebServerUseCors { get; } = new(); public WebServerUseCrossOriginPolicyArguments WebServerUseCrossOriginPolicy { get; } = new(); public WebServerUseDefaultFilesArguments WebServerUseDefaultFiles { get; } = new(); public WebServerUploadResults WebServerUploadResults { get; } = new(); public bool IsWebServerEnabled => WebServerMiddlewarePathsAndTypes.Value.Count > 0; public CommandArguments.EnableCoverageArgument EnableCoverage { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { Engine, EnginePath, EngineArgs, JSFile, ErrorPatternsFile, OutputDirectory, Timeout, ExpectedExitCode, Locale, PageLoadStrategy, SymbolMapFileArgument, SymbolicatePatternsFileArgument, SymbolicatorArgument, WebServerMiddlewarePathsAndTypes, WebServerHttpEnvironmentVariables, WebServerHttpsEnvironmentVariables, WebServerUseHttps, WebServerUseCors, WebServerUseCrossOriginPolicy, WebServerUseDefaultFiles, EnableCoverage, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WebServerCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; internal class WebServerCommandArguments : XHarnessCommandArguments, IWebServerArguments { public AppPathArgument AppPackagePath { get; } = new(); public WebServerMiddlewareArgument WebServerMiddlewarePathsAndTypes { get; } = new(); public WebServerHttpEnvironmentVariables WebServerHttpEnvironmentVariables { get; } = new(); public WebServerHttpsEnvironmentVariables WebServerHttpsEnvironmentVariables { get; } = new(); public WebServerUseHttpsArguments WebServerUseHttps { get; } = new(); public WebServerUseCorsArguments WebServerUseCors { get; } = new(); public WebServerUseCrossOriginPolicyArguments WebServerUseCrossOriginPolicy { get; } = new(); public WebServerUseDefaultFilesArguments WebServerUseDefaultFiles { get; } = new(); public WebServerUploadResults WebServerUploadResults { get; } = new(); public OutputDirectoryArgument OutputDirectory { get; } = new(); public bool IsWebServerEnabled => WebServerMiddlewarePathsAndTypes.Value.Count > 0; public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); protected override IEnumerable GetArguments() => new Argument[] { AppPackagePath, Timeout, WebServerMiddlewarePathsAndTypes, WebServerHttpEnvironmentVariables, WebServerHttpsEnvironmentVariables, WebServerUseHttps, WebServerUseCors, WebServerUseCrossOriginPolicy, WebServerUseDefaultFiles, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/CommandArguments/XHarnessCommandArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; public interface IXHarnessCommandArguments { DiagnosticsArgument Diagnostics { get; } VerbosityArgument Verbosity { get; set; } HelpArgument ShowHelp { get; } IEnumerable GetCommandArguments(); void Validate(); } public abstract class XHarnessCommandArguments : IXHarnessCommandArguments { public DiagnosticsArgument Diagnostics { get; } = new(); public VerbosityArgument Verbosity { get; set; } = new(LogLevel.Information); public HelpArgument ShowHelp { get; } = new(); public IEnumerable GetCommandArguments() => GetArguments().Concat(new Argument[] { Diagnostics, Verbosity, ShowHelp, }); public virtual void Validate() { foreach (var arg in GetCommandArguments()) { arg.Validate(); } } /// /// Returns additional option for your specific command. /// protected abstract IEnumerable GetArguments(); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidAdbCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Linq; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Android; internal class AndroidAdbCommand : XHarnessCommand { private const string Description = "Invoke bundled adb with given arguments"; protected override string CommandUsage { get; } = "android adb [OPTIONS] -- [ADB ARGUMENTS]"; protected override string CommandDescription => Description; protected override AndroidAdbCommandArguments Arguments { get; } = new(); public AndroidAdbCommand() : base(TargetPlatform.Android, "adb", false, new ServiceCollection(), Description) { } protected override Task InvokeInternal(ILogger logger) { if (!PassThroughArguments.Any()) { logger.LogError("Please provide delimeter '--' followed by arguments for ADB:" + Environment.NewLine + $" {CommandUsage}" + Environment.NewLine + $"Example:" + Environment.NewLine + $" android adb --timeout 00:01:30 -- devices -l"); return Task.FromResult(ExitCode.INVALID_ARGUMENTS); } var runner = new AdbRunner(logger); try { var result = runner.RunAdbCommand(PassThroughArguments, Arguments.Timeout); Console.Write(result.StandardOutput); Console.Error.Write(result.StandardError); return Task.FromResult((ExitCode)result.ExitCode); } catch (Exception toLog) { logger.LogCritical(toLog, $"Error: {toLog.Message}"); return Task.FromResult(ExitCode.GENERAL_FAILURE); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments; using Microsoft.DotNet.XHarness.CLI.Commands; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Android; internal abstract class AndroidCommand : XHarnessCommand where TArguments : IXHarnessCommandArguments { protected readonly Lazy _diagnosticsData; protected IDiagnosticsData DiagnosticsData => _diagnosticsData.Value; protected AndroidCommand(string name, bool allowsExtraArgs, string? help = null) : base(TargetPlatform.Android, name, allowsExtraArgs, new ServiceCollection(), help) { _diagnosticsData = new(() => Services.BuildServiceProvider().GetRequiredService()); } protected sealed override Task InvokeInternal(ILogger logger) { try { return Task.FromResult(InvokeCommand(logger)); } catch (NoDeviceFoundException noDevice) { logger.LogCritical(noDevice.Message); return Task.FromResult(ExitCode.DEVICE_NOT_FOUND); } catch (AdbFailureException adbFailure) { logger.LogCritical(adbFailure, adbFailure.Message); return Task.FromResult(ExitCode.ADB_FAILURE); } catch (Exception toLog) { logger.LogCritical(toLog, toLog.Message); } return Task.FromResult(ExitCode.GENERAL_FAILURE); } protected abstract ExitCode InvokeCommand(ILogger logger); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidCommandSet.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.CLI.Commands.Android; using Mono.Options; namespace Microsoft.DotNet.XHarness.CLI.Android; // Main Android command set that contains the plaform specific commands. // This allows the command line to support different options in different platforms. // Regardless of whether underlying behavior matches, the goal is to have the same // arguments for both platforms and have unused functionality no-op in cases where it's not needed public class AndroidCommandSet : CommandSet { public AndroidCommandSet() : base("android") { // Common verbs shared with Android Add(new AndroidTestCommand()); Add(new AndroidDeviceCommand()); Add(new AndroidInstallCommand()); Add(new AndroidRunCommand()); Add(new AndroidUninstallCommand()); Add(new AndroidAdbCommand()); Add(new AndroidStateCommand()); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidDeviceCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Android; internal class AndroidDeviceCommand : AndroidCommand { protected override AndroidDeviceCommandArguments Arguments { get; } = new() { Verbosity = new VerbosityArgument(LogLevel.Error) }; protected override string CommandUsage { get; } = "android device [OPTIONS]"; private const string CommandHelp = "Get ID of the device compatible with a given .apk / architecture"; protected override string CommandDescription { get; } = @$" {CommandHelp} Arguments: "; public AndroidDeviceCommand() : base("device", false, CommandHelp) { } protected override ExitCode InvokeCommand(ILogger logger) { IEnumerable? apkRequiredArchitecture = null; if (Arguments.DeviceArchitecture.Value.Any()) { apkRequiredArchitecture = Arguments.DeviceArchitecture.Value; } else if (!string.IsNullOrEmpty(Arguments.AppPackagePath.Value)) { if (!File.Exists(Arguments.AppPackagePath.Value)) { logger.LogCritical($"Couldn't find {Arguments.AppPackagePath.Value}!"); return ExitCode.PACKAGE_NOT_FOUND; } apkRequiredArchitecture = ApkHelper.GetApkSupportedArchitectures(Arguments.AppPackagePath.Value); } // Make sure the adb server is started var runner = new AdbRunner(logger); runner.StartAdbServer(); // enumerate the devices attached and their architectures // Tell ADB to only use that one (will always use the present one for systems w/ only 1 machine) var device = runner.GetDevice( loadApiVersion: true, loadArchitecture: true, requiredApiVersion: Arguments.ApiVersion.Value, requiredArchitectures: apkRequiredArchitecture); if (device is null) { return ExitCode.DEVICE_NOT_FOUND; } DiagnosticsData.CaptureDeviceInfo(device); Console.WriteLine(device.DeviceSerial); return ExitCode.SUCCESS; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidInstallCommand.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Android; internal class AndroidInstallCommand : AndroidCommand { protected override AndroidInstallCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "android install --package-name=... --app=... [OPTIONS]"; private const string CommandHelp = "Install an .apk on an Android device without running it"; protected override string CommandDescription { get; } = @$" {CommandHelp} Arguments: "; public AndroidInstallCommand() : base("install", false, CommandHelp) { } protected override ExitCode InvokeCommand(ILogger logger) { if (!File.Exists(Arguments.AppPackagePath)) { logger.LogCritical($"Couldn't find {Arguments.AppPackagePath}!"); return ExitCode.PACKAGE_NOT_FOUND; } var runner = new AdbRunner(logger); return InvokeHelper( logger: logger, apkPackageName: Arguments.PackageName, appPackagePath: Arguments.AppPackagePath, requestedArchitectures: Arguments.DeviceArchitecture.Value.ToList(), deviceId: Arguments.DeviceId, apiVersion: Arguments.ApiVersion.Value, bootTimeoutSeconds: Arguments.LaunchTimeout, runner: runner, DiagnosticsData); } public static ExitCode InvokeHelper( ILogger logger, string apkPackageName, string appPackagePath, IReadOnlyCollection requestedArchitectures, string? deviceId, int? apiVersion, TimeSpan bootTimeoutSeconds, AdbRunner runner, IDiagnosticsData diagnosticsData) { using (logger.BeginScope("Initialization and setup of APK on device")) { IReadOnlyCollection requiredArchitectures; var apkSupportedArchitectures = ApkHelper.GetApkSupportedArchitectures(appPackagePath); if (requestedArchitectures.Any()) { if (!apkSupportedArchitectures.Intersect(requestedArchitectures).Any()) { logger.LogError("The APK at {appPackagePath} supports {apkSupportedArchitectures} architectures " + "which does not match any of the specified architectures ({requestedArchitectures})", appPackagePath, string.Join(", ", apkSupportedArchitectures), string.Join(", ", requestedArchitectures)); return ExitCode.INVALID_ARGUMENTS; } requiredArchitectures = requestedArchitectures; } else { requiredArchitectures = apkSupportedArchitectures; } logger.LogInformation("Will attempt to find device supporting architectures: '{requiredArchitectures}'", string.Join("', '", requiredArchitectures)); // Make sure the adb server is started runner.StartAdbServer(); runner.TimeToWaitForBootCompletion = bootTimeoutSeconds; AndroidDevice? device = runner.GetDevice( loadArchitecture: true, loadApiVersion: true, deviceId, apiVersion, requiredArchitectures); if (device is null) { logger.LogWarning("No compatible device found on first attempt; trying emulator recovery..."); if (runner.TryRecoverEmulator()) { device = runner.GetDevice( loadArchitecture: true, loadApiVersion: true, deviceId, apiVersion, requiredArchitectures); } } if (device is null) { throw new NoDeviceFoundException($"Failed to find compatible device: {string.Join(", ", requiredArchitectures)}"); } diagnosticsData.CaptureDeviceInfo(device); // Wait till at least device(s) are ready if (!runner.WaitForDevice()) { return ExitCode.DEVICE_NOT_FOUND; } logger.LogDebug($"Working with {device.DeviceSerial} (API {device.ApiVersion})"); runner.CheckPackageVerificationSettings(); // If anything changed about the app, Install will fail; uninstall it first. // (we'll ignore if it's not present) // This is where mismatched architecture APKs fail. runner.UninstallApk(apkPackageName); if (runner.InstallApk(appPackagePath) != 0) { logger.LogCritical("Install failure: Test command cannot continue"); runner.UninstallApk(apkPackageName); return ExitCode.PACKAGE_INSTALLATION_FAILURE; } runner.KillApk(apkPackageName); } return ExitCode.SUCCESS; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidRunCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Android; internal class AndroidRunCommand : AndroidCommand { protected override AndroidRunCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "android run --output-directory=... --package-name=... [OPTIONS]"; private const string CommandHelp = "Run tests using an already installed .apk on an Android device"; protected override string CommandDescription { get; } = @$" {CommandHelp} APKs can communicate status back to XHarness using the parameters: Required: {InstrumentationRunner.ReturnCodeVariableName} - Exit code for instrumentation. Necessary because a crashing instrumentation may be indistinguishable from a passing one based solely on the exit code. Arguments: "; public AndroidRunCommand() : base("run", false, CommandHelp) { } protected override ExitCode InvokeCommand(ILogger logger) { var runner = new AdbRunner(logger); // Make sure the adb server is started runner.StartAdbServer(); runner.TimeToWaitForBootCompletion = Arguments.LaunchTimeout; var device = string.IsNullOrEmpty(Arguments.DeviceId.Value) ? runner.GetSingleDevice(loadArchitecture: true, loadApiVersion: true, requiredInstalledApp: "package:" + Arguments.PackageName) : runner.GetSingleDevice(loadArchitecture: true, loadApiVersion: true, requiredDeviceId: Arguments.DeviceId.Value); if (device is null) { logger.LogWarning("No compatible device found on first attempt; trying emulator recovery..."); if (runner.TryRecoverEmulator()) { device = string.IsNullOrEmpty(Arguments.DeviceId.Value) ? runner.GetSingleDevice(loadArchitecture: true, loadApiVersion: true, requiredInstalledApp: "package:" + Arguments.PackageName) : runner.GetSingleDevice(loadArchitecture: true, loadApiVersion: true, requiredDeviceId: Arguments.DeviceId.Value); } } if (device is null) { return ExitCode.DEVICE_NOT_FOUND; } DiagnosticsData.CaptureDeviceInfo(device); // Wait till at least device(s) are ready if (!runner.WaitForDevice()) { return ExitCode.DEVICE_NOT_FOUND; } logger.LogDebug($"Working with API {runner.GetAdbVersion()}"); // Empty log as we'll be uploading the full logcat for this execution runner.ClearAdbLog(); if (Arguments.Wifi != WifiStatus.Unknown) { runner.EnableWifi(Arguments.Wifi == WifiStatus.Enable); } if (Arguments.EnableCoverage) { Arguments.InstrumentationArguments.Value["enable-coverage"] = "true"; } var instrumentationRunner = new InstrumentationRunner(logger, runner); return instrumentationRunner.RunApkInstrumentation( Arguments.PackageName, Arguments.InstrumentationName, Arguments.InstrumentationArguments, Arguments.OutputDirectory, Arguments.DeviceOutputFolder, Arguments.Timeout, Arguments.ExpectedExitCode); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidStateCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.DotNet.XHarness.CLI.Commands.Android; internal class AndroidStateCommand : GetStateCommand { protected override string CommandUsage { get; } = "android state"; protected override AndroidStateCommandArguments Arguments { get; } = new(); public AndroidStateCommand() : base(TargetPlatform.Android, new ServiceCollection()) { } protected override Task InvokeInternal(ILogger logger) { try { var data = GetStateData(Arguments.UseJson ? NullLogger.Instance : logger); if (Arguments.UseJson) { var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, WriteIndented = true, }; JsonSerializer.Serialize(Console.OpenStandardOutput(), data, options); return Task.FromResult(ExitCode.SUCCESS); } var state = data.DeviceState switch { "device" => "Device/emulator is ready", null or "" => "No device attached", _ => data.DeviceState, }; void PrintAndroidDevice(AndroidDevice device) { logger.LogInformation($"{device.DeviceSerial}:{Environment.NewLine}" + $" Architecture: {device.Architecture}{Environment.NewLine}" + $" API version: {device.ApiVersion}{Environment.NewLine}" + $" Supported architectures: {string.Join(", ", device?.SupportedArchitectures ?? Array.Empty())}"); } logger.LogInformation($"ADB Version info:{Environment.NewLine}{string.Join(Environment.NewLine, data.AdbVersion)}"); logger.LogInformation($"ADB State:{Environment.NewLine}{state}"); if (data.Emulators.Any()) { logger.LogInformation($"List of emulators:"); foreach (AndroidDevice emulator in data.Emulators) { PrintAndroidDevice(emulator); } } if (data.Devices.Any()) { logger.LogInformation($"List of devices:"); foreach (AndroidDevice device in data.Devices) { PrintAndroidDevice(device); } } return Task.FromResult(ExitCode.SUCCESS); } catch (Exception toLog) { logger.LogCritical(toLog, $"Error: {toLog.Message}"); return Task.FromResult(ExitCode.GENERAL_FAILURE); } } private static StateData GetStateData(ILogger logger) { var runner = new AdbRunner(logger); logger.LogDebug("Getting state of ADB and attached Android device(s)"); var adbVersion = runner.GetAdbVersion() .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var state = runner.GetAdbState().Trim(); IReadOnlyCollection allDevices = runner.GetDevices(); var emulators = allDevices.Where(d => d.DeviceSerial.StartsWith("emulator")); var devices = allDevices.Except(emulators); return new StateData(state, adbVersion, emulators, devices); } private record StateData( string DeviceState, string[] AdbVersion, IEnumerable Emulators, IEnumerable Devices); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidTestCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Linq; using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Android; internal class AndroidTestCommand : AndroidCommand { private const string ReturnCodeVariableName = "return-code"; protected override AndroidTestCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "android test --output-directory=... --package-name=... --app=... [OPTIONS]"; private const string CommandHelp = "Executes test .apk on an Android device, waits up to a given timeout, then copies files off the device and uninstalls the test app"; protected override string CommandDescription { get; } = @$" {CommandHelp} APKs can communicate status back to XHarness using the parameters: Required: {ReturnCodeVariableName} - Exit code for instrumentation. Necessary because a crashing instrumentation may be indistinguishable from a passing one from exit codes. Arguments: "; public AndroidTestCommand() : base("test", false, CommandHelp) { } protected override ExitCode InvokeCommand(ILogger logger) { var runner = new AdbRunner(logger); var exitCode = AndroidInstallCommand.InvokeHelper( logger: logger, apkPackageName: Arguments.PackageName, appPackagePath: Arguments.AppPackagePath, requestedArchitectures: Arguments.DeviceArchitecture.Value.ToList(), deviceId: Arguments.DeviceId.Value, apiVersion: Arguments.ApiVersion.Value, bootTimeoutSeconds: Arguments.LaunchTimeout, runner, DiagnosticsData); if (Arguments.Wifi != WifiStatus.Unknown) { runner.EnableWifi(Arguments.Wifi == WifiStatus.Enable); } try { if (exitCode == ExitCode.SUCCESS) { runner.ClearAdbLog(); if (Arguments.EnableCoverage) { Arguments.InstrumentationArguments.Value["enable-coverage"] = "true"; } var instrumentationRunner = new InstrumentationRunner(logger, runner); exitCode = instrumentationRunner.RunApkInstrumentation( Arguments.PackageName, Arguments.InstrumentationName, Arguments.InstrumentationArguments, Arguments.OutputDirectory, Arguments.DeviceOutputFolder, Arguments.Timeout, Arguments.ExpectedExitCode); } } finally { runner.UninstallApk(Arguments.PackageName); } return exitCode; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidUninstallCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Android; internal class AndroidUninstallCommand : AndroidCommand { protected override AndroidUninstallCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "android uninstall --package-name=... [OPTIONS]"; private const string CommandHelp = "Uninstall an .apk from an Android device"; protected override string CommandDescription { get; } = @$" {CommandHelp} Arguments: "; public AndroidUninstallCommand() : base("uninstall", false, CommandHelp) { } protected override ExitCode InvokeCommand(ILogger logger) { using (logger.BeginScope("Find device where to uninstall APK")) { // Make sure the adb server is started var runner = new AdbRunner(logger); runner.StartAdbServer(); AndroidDevice? device = runner.GetSingleDevice( loadArchitecture: true, loadApiVersion: true, requiredDeviceId: Arguments.DeviceId, requiredInstalledApp: "package:" + Arguments.PackageName); if (device is null) { return ExitCode.DEVICE_NOT_FOUND; } DiagnosticsData.CaptureDeviceInfo(device); logger.LogDebug($"Working with {device.DeviceSerial} (API {device.ApiVersion})"); runner.UninstallApk(Arguments.PackageName); return ExitCode.SUCCESS; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Android/IDiagnosticDataExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.Common; namespace Microsoft.DotNet.XHarness.CLI.Android; internal static class IDiagnosticDataExtensions { public static void CaptureDeviceInfo(this IDiagnosticsData data, AndroidDevice device) { data.Target = device.Architecture; data.TargetOS = "API " + device.ApiVersion; data.Device = device.DeviceSerial; data.IsDevice = !device.DeviceSerial.ToLowerInvariant().StartsWith("emulator"); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/AndroidHeadless/AndroidHeadlessCommandSet.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.CLI.Commands.Android; using Microsoft.DotNet.XHarness.CLI.Commands.AndroidHeadless; using Mono.Options; namespace Microsoft.DotNet.XHarness.CLI.AndroidHeadless; // Main Android command set that contains the plaform specific commands. // This allows the command line to support different options in different platforms. // Regardless of whether underlying behavior matches, the goal is to have the same // arguments for both platforms and have unused functionality no-op in cases where it's not needed public class AndroidHeadlessCommandSet : CommandSet { public AndroidHeadlessCommandSet() : base("android-headless") { // Common verbs shared with Android Add(new AndroidHeadlessTestCommand()); Add(new AndroidHeadlessInstallCommand()); Add(new AndroidHeadlessRunCommand()); Add(new AndroidHeadlessUninstallCommand()); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/AndroidHeadless/AndroidHeadlessInstallCommand.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.AndroidHeadless; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.AndroidHeadless; internal class AndroidHeadlessInstallCommand : AndroidCommand { protected override AndroidHeadlessInstallCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "android-headless install --test-folder=... [OPTIONS]"; private const string CommandHelp = "Install a test folder to an Android device without running it"; protected override string CommandDescription { get; } = @$" {CommandHelp} Arguments: "; public AndroidHeadlessInstallCommand() : base("install", false, CommandHelp) { } protected override ExitCode InvokeCommand(ILogger logger) { if (!Directory.Exists(Arguments.TestPath)) { logger.LogCritical($"Couldn't find test {Arguments.TestPath}!"); return ExitCode.PACKAGE_NOT_FOUND; } if (!Directory.Exists(Arguments.RuntimePath)) { logger.LogCritical($"Couldn't find shared runtime {Arguments.RuntimePath}!"); return ExitCode.PACKAGE_NOT_FOUND; } var runner = new AdbRunner(logger); List testRequiredArchitecture = new(); if (string.IsNullOrEmpty(Arguments.DeviceId)) { // trying to choose suitable device if (Arguments.DeviceArchitecture.Value.Any()) { testRequiredArchitecture = Arguments.DeviceArchitecture.Value.ToList(); logger.LogInformation($"Will attempt to run device on specified architecture: '{string.Join("', '", testRequiredArchitecture)}'"); } } return InvokeHelper( logger: logger, testPath: Arguments.TestPath, runtimePath: Arguments.RuntimePath, testRequiredArchitecture: testRequiredArchitecture, deviceId: Arguments.DeviceId, apiVersion: Arguments.ApiVersion.Value, bootTimeoutSeconds: Arguments.LaunchTimeout, runner: runner, DiagnosticsData); } public static ExitCode InvokeHelper( ILogger logger, string testPath, string runtimePath, IEnumerable testRequiredArchitecture, string? deviceId, int? apiVersion, TimeSpan bootTimeoutSeconds, AdbRunner runner, IDiagnosticsData diagnosticsData) { using (logger.BeginScope("Initialization and setup of test on device")) { // Make sure the adb server is started runner.StartAdbServer(); runner.TimeToWaitForBootCompletion = bootTimeoutSeconds; AndroidDevice? device = runner.GetDevice( loadArchitecture: true, loadApiVersion: true, deviceId, apiVersion, testRequiredArchitecture); if (device is null) { logger.LogWarning("No compatible device found on first attempt; trying emulator recovery..."); if (runner.TryRecoverEmulator()) { device = runner.GetDevice( loadArchitecture: true, loadApiVersion: true, deviceId, apiVersion, testRequiredArchitecture); } } if (device is null) { throw new NoDeviceFoundException($"Failed to find compatible device: {string.Join(", ", testRequiredArchitecture)}"); } diagnosticsData.CaptureDeviceInfo(device); // Wait till at least device(s) are ready if (!runner.WaitForDevice()) { return ExitCode.DEVICE_NOT_FOUND; } logger.LogDebug($"Working with {device.DeviceSerial} (API {device.ApiVersion})"); // If anything changed about the app, Install will fail; uninstall it first. // (we'll ignore if it's not present) // This is where mismatched architecture APKs fail. runner.DeleteHeadlessFolder(testPath); runner.DeleteHeadlessFolder("runtime"); if (runner.CopyHeadlessFolder(testPath) != 0) { logger.LogCritical("Install failure: Test command cannot continue"); runner.DeleteHeadlessFolder(testPath); return ExitCode.PACKAGE_INSTALLATION_FAILURE; } if (runner.CopyHeadlessFolder(runtimePath, true) != 0) { logger.LogCritical("Install failure: Test command cannot continue"); runner.DeleteHeadlessFolder("runtime"); return ExitCode.PACKAGE_INSTALLATION_FAILURE; } } return ExitCode.SUCCESS; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/AndroidHeadless/AndroidHeadlessRunCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.Android.Execution; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.AndroidHeadless; internal class AndroidHeadlessRunCommand : AndroidCommand { protected override AndroidHeadlessRunCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "android-headless run --output-directory=... --test-assembly=... [OPTIONS]"; private const string CommandHelp = "Run tests using an already installed executable on an Android device"; protected override string CommandDescription { get; } = @$" {CommandHelp} Arguments: "; public AndroidHeadlessRunCommand() : base("run", false, CommandHelp) { } protected override ExitCode InvokeCommand(ILogger logger) { var runner = new AdbRunner(logger); // Make sure the adb server is started runner.StartAdbServer(); var device = string.IsNullOrEmpty(Arguments.DeviceId.Value) ? runner.GetSingleDevice(loadArchitecture: true, loadApiVersion: true, requiredInstalledApp: "filename:" + Arguments.TestPath) : runner.GetSingleDevice(loadArchitecture: true, loadApiVersion: true, requiredDeviceId: Arguments.DeviceId.Value); if (device is null) { return ExitCode.DEVICE_NOT_FOUND; } DiagnosticsData.CaptureDeviceInfo(device); runner.TimeToWaitForBootCompletion = Arguments.LaunchTimeout; // Wait till at least device(s) are ready if (!runner.WaitForDevice()) { return ExitCode.DEVICE_NOT_FOUND; } return InvokeHelper( logger, Arguments.TestPath, Arguments.RuntimePath, Arguments.TestAssembly, Arguments.TestScript, Arguments.OutputDirectory, Arguments.Timeout, Arguments.ExpectedExitCode, Arguments.Wifi, runner); } public static ExitCode InvokeHelper( ILogger logger, string testPath, string runtimePath, string testAssembly, string testScript, string outputDirectory, TimeSpan timeout, int expectedExitCode, WifiStatus wifi, AdbRunner runner) { logger.LogDebug($"Working with API {runner.GetAdbVersion()}"); // Empty log as we'll be uploading the full logcat for this execution runner.ClearAdbLog(); if (wifi != WifiStatus.Unknown) { runner.EnableWifi(wifi == WifiStatus.Enable); } // No class name = default Instrumentation ProcessExecutionResults? result = runner.RunHeadlessCommand( testPath, runtimePath, testAssembly, testScript, timeout); bool failurePullingFiles = false; using (logger.BeginScope("Post-test copy and cleanup")) { // Optionally copy off an entire folder if (!string.IsNullOrEmpty(outputDirectory)) { var testResultPath = AdbRunner.GlobalReadWriteDirectory + Path.AltDirectorySeparatorChar + new DirectoryInfo(testPath).Name + Path.AltDirectorySeparatorChar + "testResults.xml"; try { var logs = runner.HeadlessPullFiles(testResultPath, outputDirectory); logger.LogDebug($"Found log file testResults.xml"); } catch (Exception toLog) { logger.LogError(toLog, "Hit error (typically permissions) trying to pull {testResultPath}", outputDirectory); failurePullingFiles = true; } } runner.TryDumpAdbLog(Path.Combine(outputDirectory, $"adb-logcat-{testAssembly}-default.log")); } if (failurePullingFiles) { logger.LogError($"Received expected exit code ({ExitCode.SUCCESS}), " + "but we hit errors pulling files from the device (see log for details.)"); return ExitCode.DEVICE_FILE_COPY_FAILURE; } if (result.ExitCode != expectedExitCode) { logger.LogError($"Non-success exit code: {result.ExitCode}, expected: {expectedExitCode}"); if (result.ExitCode != (int)ExitCode.TESTS_FAILED) { logger.LogError($"Unexpected test run failure, extracting detailed diagnostics"); runner.DumpBugReport(Path.Combine(outputDirectory, $"adb-bugreport-{testAssembly}")); } return (ExitCode)result.ExitCode; } return ExitCode.SUCCESS; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/AndroidHeadless/AndroidHeadlessTestCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.AndroidHeadless; internal class AndroidHeadlessTestCommand : AndroidCommand { protected override AndroidHeadlessTestCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "android-headless test --output-directory=... --test-folder=... --test-command=... [OPTIONS]"; private const string CommandHelp = "Executes test executable on an Android device, waits up to a given timeout, then copies files off the device and uninstalls the test app"; protected override string CommandDescription { get; } = @$" {CommandHelp} Arguments: "; public AndroidHeadlessTestCommand() : base("test", false, CommandHelp) { } protected override ExitCode InvokeCommand(ILogger logger) { if (!Directory.Exists(Arguments.TestPath)) { logger.LogCritical($"Couldn't find test {Arguments.TestPath}!"); return ExitCode.PACKAGE_NOT_FOUND; } if (!Directory.Exists(Arguments.RuntimePath)) { logger.LogCritical($"Couldn't find shared runtime {Arguments.RuntimePath}!"); return ExitCode.PACKAGE_NOT_FOUND; } IEnumerable testRequiredArchitecture = Arguments.DeviceArchitecture.Value; logger.LogInformation($"Required architecture: '{string.Join("', '", testRequiredArchitecture)}'"); var runner = new AdbRunner(logger); var exitCode = AndroidHeadlessInstallCommand.InvokeHelper( logger: logger, testPath: Arguments.TestPath, runtimePath: Arguments.RuntimePath, testRequiredArchitecture: testRequiredArchitecture, deviceId: Arguments.DeviceId.Value, apiVersion: Arguments.ApiVersion.Value, bootTimeoutSeconds: Arguments.LaunchTimeout, runner, DiagnosticsData); if (exitCode == ExitCode.SUCCESS) { exitCode = AndroidHeadlessRunCommand.InvokeHelper( logger: logger, testPath: Arguments.TestPath, runtimePath: Arguments.RuntimePath, testAssembly: Arguments.TestAssembly, testScript: Arguments.TestScript, outputDirectory: Arguments.OutputDirectory, timeout: Arguments.Timeout, expectedExitCode: Arguments.ExpectedExitCode, wifi: Arguments.Wifi, runner: runner); } runner.DeleteHeadlessFolder(Arguments.TestPath); runner.DeleteHeadlessFolder("runtime"); return exitCode; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/AndroidHeadless/AndroidHeadlessUninstallCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Android; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.AndroidHeadless; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments.AndroidHeadless; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.AndroidHeadless; internal class AndroidHeadlessUninstallCommand : AndroidCommand { protected override AndroidHeadlessUninstallCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "android-headless uninstall --test-folder=... [OPTIONS]"; private const string CommandHelp = "Uninstall a test folder from an Android device"; protected override string CommandDescription { get; } = @$" {CommandHelp} Arguments: "; public AndroidHeadlessUninstallCommand() : base("uninstall", false, CommandHelp) { } protected override ExitCode InvokeCommand(ILogger logger) { using (logger.BeginScope("Find device where to uninstall folder")) { // Make sure the adb server is started var runner = new AdbRunner(logger); runner.StartAdbServer(); AndroidDevice? device = runner.GetSingleDevice( loadArchitecture: true, loadApiVersion: true, requiredDeviceId: Arguments.DeviceId, requiredInstalledApp: "filename:" + Arguments.TestPath); if (device is null) { return ExitCode.DEVICE_NOT_FOUND; } DiagnosticsData.CaptureDeviceInfo(device); logger.LogDebug($"Working with {device.DeviceSerial} (API {device.ApiVersion})"); runner.DeleteHeadlessFolder(Arguments.TestPath); runner.DeleteHeadlessFolder("runtime"); return ExitCode.SUCCESS; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleAppCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Apple; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; internal abstract class AppleAppCommand : AppleCommand where TArguments : IAppleAppRunArguments { protected readonly ErrorKnowledgeBase ErrorKnowledgeBase = new(); protected AppleAppCommand(string name, bool allowsExtraArgs, IServiceCollection services, string? help = null) : base(name, allowsExtraArgs, services, help) { } protected sealed override async Task Invoke(Extensions.Logging.ILogger logger) { var targetName = Arguments.Target.Value.AsString(); logger.LogInformation("Preparing run for {target}", targetName + (!string.IsNullOrEmpty(Arguments.DeviceName.Value) ? " targeting " + Arguments.DeviceName.Value : null)); // Create main log file for the run using ILogs logs = new Logs(Arguments.OutputDirectory); string logFileName = $"{Name}-{targetName}{(!string.IsNullOrEmpty(Arguments.DeviceName.Value) ? "-" + Arguments.DeviceName.Value : null)}.log"; IFileBackedLog runLog = logs.Create(logFileName, LogType.ExecutionLog.ToString(), timestamp: true); // Pipe the execution log to the debug output of XHarness effectively making "-v" turn this on CallbackLog debugLog = new(message => logger.LogDebug(message.Trim())); using var mainLog = Log.CreateReadableAggregatedLog(runLog, debugLog); Services.TryAddSingleton(logs); Services.TryAddTransient(); Services.TryAddSingleton(mainLog); Services.TryAddSingleton(mainLog); Services.TryAddSingleton(mainLog); var serviceProvider = Services.BuildServiceProvider(); var diagnosticsData = serviceProvider.GetRequiredService(); diagnosticsData.Target = Arguments.Target.Value.AsString(); diagnosticsData.IsDevice = !Arguments.Target.Value.Platform.IsSimulator(); var cts = new CancellationTokenSource(); cts.CancelAfter(Arguments.Timeout); cts.Token.Register(() => { logger.LogError("Run timed out after {timeout} seconds", Math.Ceiling(Arguments.Timeout.Value.TotalSeconds)); }); return await InvokeInternal(serviceProvider, cts.Token); } protected abstract Task InvokeInternal(ServiceProvider serviceProvider, CancellationToken cancellationToken); [SuppressMessage("Usage", "CA2254:The logging message template should not vary between calls to LoggerExtensions", Justification = "This is just a simple shim")] protected class ConsoleLogger : XHarness.Apple.ILogger { private readonly Extensions.Logging.ILogger _logger; public ConsoleLogger(Extensions.Logging.ILogger logger) { _logger = logger; } public void LogDebug(string message) => _logger.LogDebug(message); public void LogInformation(string message) => _logger.LogInformation(message); public void LogWarning(string message) => _logger.LogWarning(message); public void LogError(string message) => _logger.LogError(message); public void LogCritical(string message) => _logger.LogCritical(message); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Threading.Tasks; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; internal abstract class AppleCommand : XHarnessCommand where TArguments : IAppleArguments { protected AppleCommand(string name, bool allowsExtraArgs, IServiceCollection services, string? help = null) : base(TargetPlatform.Apple, name, allowsExtraArgs, services, help) { } protected sealed override Task InvokeInternal(ILogger logger) { var processManager = new MlaunchProcessManager(Arguments.XcodeRoot, Arguments.MlaunchPath); Services.TryAddSingleton(processManager); Services.TryAddSingleton(processManager); Services.TryAddSingleton(processManager); return Invoke(logger); } protected abstract Task Invoke(ILogger logger); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleCommandSet.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Apple; using Microsoft.DotNet.XHarness.CLI.Commands.Apple.Simulators; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; using Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Mono.Options; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; internal class AppleCommandSet : CommandSet { public AppleCommandSet() : base("apple") { var services = GetAppleDependencies(); // Commands for full install/execute/uninstall flows Add(new AppleTestCommand(services)); Add(new AppleRunCommand(services)); // Commands for more fine grained control over the separate operations Add(new AppleInstallCommand(services)); Add(new AppleUninstallCommand(services)); Add(new AppleJustTestCommand(services)); Add(new AppleJustRunCommand(services)); // Commands for getting information Add(new AppleDeviceCommand(services)); Add(new AppleMlaunchCommand(services)); Add(new AppleStateCommand()); // Commands for simulator management Add(new SimulatorsCommandSet()); } public static IServiceCollection GetAppleDependencies() { var services = new ServiceCollection(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); return services; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleDeviceCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Linq; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Apple; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; internal class AppleDeviceCommand : AppleCommand { protected override AppleDeviceCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "apple device [OPTIONS] [TARGET]"; private const string CommandHelp = "Finds the UDID of a device/simulator for given target"; protected override string CommandDescription { get; } = @$" {CommandHelp} Arguments: "; public AppleDeviceCommand(IServiceCollection services) : base("device", true, services, CommandHelp) { } protected override async Task Invoke(Extensions.Logging.ILogger logger) { var log = new CallbackLog(m => logger.LogDebug(m)); TestTargetOs target; try { target = ParseTarget(); } catch (Exception e) { logger.LogError(e.Message); return ExitCode.INVALID_ARGUMENTS; } var serviceProvider = Services.BuildServiceProvider(); var deviceFinder = serviceProvider.GetRequiredService(); var diagnosticsData = serviceProvider.GetRequiredService(); diagnosticsData.Target = target.AsString(); try { var device = (await deviceFinder.FindDevice(target, Arguments.DeviceName, log, Arguments.IncludeWireless)).Device; diagnosticsData.TargetOS = device.OSVersion.Split(' ', 2).Last(); diagnosticsData.Device = device.Name ?? device.UDID; diagnosticsData.IsDevice = !target.Platform.IsSimulator(); Console.WriteLine(device.UDID); } catch (Exception e) { logger.LogError(e.ToString()); return ExitCode.DEVICE_NOT_FOUND; } return ExitCode.SUCCESS; } private TestTargetOs ParseTarget() { if (ExtraArguments.Count() != 1) { throw new ArgumentException("You have to specify one target platform"); } var target = new TargetArgument(); target.Action(ExtraArguments.First()); target.Validate(); if (target.Value.Platform == TestTarget.MacCatalyst) { throw new ArgumentException("Target maccatalyst is not supported for this command"); } return target.Value; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleInstallCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Apple; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; internal class AppleInstallCommand : AppleAppCommand { private const string CommandHelp = "Installs a given iOS/tvOS/watchOS/xrOS/MacCatalyst application bundle in a target device/simulator"; protected override AppleInstallCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "apple install --app=... --output-directory=... --target=... [OPTIONS] [-- [RUNTIME ARGUMENTS]]"; protected override string CommandDescription { get; } = CommandHelp; public AppleInstallCommand(IServiceCollection services) : base("install", false, services, CommandHelp) { } protected override Task InvokeInternal(ServiceProvider serviceProvider, CancellationToken cancellationToken) => serviceProvider.GetRequiredService() .OrchestrateInstall( Arguments.Target, Arguments.DeviceName, Arguments.AppBundlePath, Arguments.Timeout, Arguments.IncludeWireless, Arguments.ResetSimulator, enableLldb: false, cancellationToken); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleJustRunCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Apple; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; internal class AppleJustRunCommand : AppleAppCommand { private const string CommandHelp = "Runs an already installed iOS/tvOS/watchOS/xrOS/MacCatalyst test application containing a TestRunner " + "in a target device/simulator and tries to detect the exit code."; protected override string CommandUsage { get; } = "apple just-run --app=... --output-directory=... --target=... [OPTIONS] [-- [RUNTIME ARGUMENTS]]"; protected override string CommandDescription { get; } = CommandHelp; protected override AppleJustRunCommandArguments Arguments { get; } = new(); public AppleJustRunCommand(IServiceCollection services) : base("just-run", false, services, CommandHelp) { } protected override Task InvokeInternal(ServiceProvider serviceProvider, CancellationToken cancellationToken) => serviceProvider.GetRequiredService() .OrchestrateRun( bundleIdentifier: Arguments.BundleIdentifier, target: Arguments.Target, deviceName: Arguments.DeviceName, timeout: Arguments.Timeout, launchTimeout: Arguments.Timeout, expectedExitCode: Arguments.ExpectedExitCode, includeWirelessDevices: Arguments.IncludeWireless, enableLldb: Arguments.EnableLldb, signalAppEnd: Arguments.SignalAppEnd, waitForExit: !Arguments.NoWait, environmentalVariables: Arguments.EnvironmentalVariables.Value, passthroughArguments: PassThroughArguments, cancellationToken); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleJustTestCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Apple; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; internal class AppleJustTestCommand : AppleAppCommand { private const string CommandHelp = "Runs an already installed iOS/tvOS/watchOS/xrOS/MacCatalyst test application containing a TestRunner in a target device/simulator."; protected override string CommandUsage { get; } = "apple just-test --app=... --output-directory=... --target=... [OPTIONS] [-- [RUNTIME ARGUMENTS]]"; protected override string CommandDescription { get; } = CommandHelp; protected override AppleJustTestCommandArguments Arguments { get; } = new(); public AppleJustTestCommand(IServiceCollection services) : base("just-test", false, services, CommandHelp) { } protected override Task InvokeInternal(ServiceProvider serviceProvider, CancellationToken cancellationToken) { IReadOnlyCollection<(string, string?)> envVars = Arguments.EnvironmentalVariables.Value; if (Arguments.EnableCoverage) { var coverageVars = new List<(string, string?)>(envVars) { ("NUNIT_ENABLE_COVERAGE", "true"), ("NUNIT_COVERAGE_OUTPUT_PATH", "coverage.cobertura.xml"), }; envVars = coverageVars; } return serviceProvider.GetRequiredService() .OrchestrateTest( Arguments.BundleIdentifier, Arguments.Target, Arguments.DeviceName, Arguments.Timeout, Arguments.LaunchTimeout, Arguments.CommunicationChannel, Arguments.XmlResultJargon, Arguments.SingleMethodFilters.Value, Arguments.ClassMethodFilters.Value, includeWirelessDevices: Arguments.IncludeWireless, enableLldb: Arguments.EnableLldb, signalAppEnd: Arguments.SignalAppEnd, envVars, PassThroughArguments, cancellationToken); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleMlaunchCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; internal class AppleMlaunchCommand : AppleCommand { private const string Description = "Invoke bundled mlaunch with given arguments"; protected override string CommandUsage { get; } = "apple mlaunch [OPTIONS] -- [MLAUNCH ARGUMENTS]"; protected override string CommandDescription => Description; protected override AppleMlaunchCommandArguments Arguments { get; } = new(); public AppleMlaunchCommand(IServiceCollection services) : base("mlaunch", false, services, Description) { } protected override async Task Invoke(ILogger logger) { if (!PassThroughArguments.Any()) { logger.LogError("Please provide delimeter '--' followed by arguments for ADB:" + Environment.NewLine + $" {CommandUsage}" + Environment.NewLine + $"Example:" + Environment.NewLine + $" apple mlaunch --timeout 00:01:30 -- devices -l"); return ExitCode.INVALID_ARGUMENTS; } var processManager = Services.BuildServiceProvider().GetRequiredService(); try { var nullLog = new CallbackLog(s => { }); var stdout = new CallbackLog(Console.Write); var stderr = new CallbackLog(Console.Error.Write); var args = new MlaunchArguments(PassThroughArguments.Select(arg => new SimpleMlaunchArgument(arg)).ToArray()); var cts = new CancellationTokenSource(); cts.CancelAfter(Arguments.Timeout); var result = await processManager.ExecuteCommandAsync( args, Arguments.Verbosity < LogLevel.Information ? stdout : nullLog, Arguments.Verbosity <= LogLevel.Warning ? stdout : nullLog, stderr, Arguments.Timeout, Arguments.EnvironmentalVariables.Value.ToDictionary(t => t.Item1, t => t.Item2), verbosity: 0, // -v needs to be supplied by user cts.Token); if (result.TimedOut) { return ExitCode.TIMED_OUT; } return (ExitCode)result.ExitCode; } catch (Exception e) { logger.LogError(e.ToString()); return ExitCode.GENERAL_FAILURE; } } // This is needed because ProcessManagers accepts MlaunchArguments only which are strong-typed args supported by mlaunch // Since in this command, these are supplied by user, we need to forward them as-is private class SimpleMlaunchArgument : iOS.Shared.Execution.MlaunchArgument { private readonly string _argument; public SimpleMlaunchArgument(string argument) { _argument = argument; } public override string AsCommandLineArgument() => Escape(_argument); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleResetSimulatorCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Apple; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; internal class AppleResetSimulatorCommand : AppleAppCommand { private const string CommandHelp = "Resets given iOS/tvOS simulator (wipes it clean)"; protected override AppleResetSimulatorCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "apple reset-simulator --target=... --output-directory=... [OPTIONS]"; protected override string CommandDescription { get; } = CommandHelp; public AppleResetSimulatorCommand(IServiceCollection services) : base("reset-simulator", false, services, CommandHelp) { } protected override Task InvokeInternal(ServiceProvider serviceProvider, CancellationToken cancellationToken) => serviceProvider.GetRequiredService() .OrchestrateSimulatorReset(Arguments.Target, Arguments.DeviceName, Arguments.Timeout, cancellationToken); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleRunCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Apple; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; /// /// Command which executes a given, already-packaged iOS application, waits on it and returns status based on the outcome. /// internal class AppleRunCommand : AppleAppCommand { private const string CommandHelp = "Installs, runs and uninstalls a given iOS/tvOS/watchOS/xrOS/MacCatalyst application bundle " + "in a target device/simulator and tries to detect the exit code."; protected override AppleRunCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "apple run --app=... --output-directory=... --target=... [OPTIONS] [-- [RUNTIME ARGUMENTS]]"; protected override string CommandDescription { get; } = CommandHelp; public AppleRunCommand(IServiceCollection services) : base("run", false, services, CommandHelp) { } protected override Task InvokeInternal(ServiceProvider serviceProvider, CancellationToken cancellationToken) => serviceProvider.GetRequiredService() .OrchestrateRun( appBundlePath: Arguments.AppBundlePath, target: Arguments.Target, deviceName: Arguments.DeviceName, timeout: Arguments.Timeout, launchTimeout: Arguments.LaunchTimeout, expectedExitCode: Arguments.ExpectedExitCode, includeWirelessDevices: Arguments.IncludeWireless, resetSimulator: Arguments.ResetSimulator, enableLldb: Arguments.EnableLldb, signalAppEnd: Arguments.SignalAppEnd, waitForExit: !Arguments.NoWait, environmentalVariables: Arguments.EnvironmentalVariables.Value, passthroughArguments: PassThroughArguments, cancellationToken); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleStateCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Apple; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; internal class AppleStateCommand : GetStateCommand { protected override string CommandUsage { get; } = "ios state [OPTIONS]"; private class DeviceInfo { public string Name { get; } public string UDID { get; } public string Type { get; } public string OSVersion { get; } public bool IsPaired { get; } public DeviceInfo(string name, string uDID, string type, string oSVersion, bool isPaired = true) { Name = name; UDID = uDID; Type = type; OSVersion = oSVersion; IsPaired = isPaired; } } private class SystemInfo { public string MachineName { get; } public string OSName { get; } public string OSVersion { get; } public string OSPlatform { get; } public string XcodePath { get; } public string XcodeVersion { get; } public string MlaunchPath { get; } public string MlaunchVersion { get; } public List Simulators { get; } = new List(); public List Devices { get; } = new List(); public SystemInfo(string machineName, string oSName, string oSVersion, string oSPlatform, string xcodePath, string xcodeVersion, string mlaunchPath, string mlaunchVersion) { MachineName = machineName; OSName = oSName; OSVersion = oSVersion; OSPlatform = oSPlatform; XcodePath = xcodePath; XcodeVersion = xcodeVersion; MlaunchPath = mlaunchPath; MlaunchVersion = mlaunchVersion; } } private const string SimulatorPrefix = "com.apple.CoreSimulator.SimDeviceType."; public AppleStateCommand() : base(TargetPlatform.Apple, new ServiceCollection()) { } protected override AppleStateCommandArguments Arguments { get; } = new(); private static async Task AsJson(SystemInfo info) { var options = new JsonSerializerOptions { WriteIndented = true }; await JsonSerializer.SerializeAsync(Console.OpenStandardOutput(), info, options); Console.WriteLine(); } private void AsText(SystemInfo info) { Console.WriteLine("Runtime Enviroment:"); Console.WriteLine($" Machine name:\t{info.MachineName}"); Console.WriteLine($" OS Name:\t{info.OSName}"); Console.WriteLine($" OS Version:\t{info.OSVersion}"); Console.WriteLine($" OS Platform:\t{info.OSPlatform}"); Console.WriteLine(); Console.WriteLine("Developer Tools:"); Console.WriteLine($" Xcode:\t{info.XcodeVersion} - {info.XcodePath}"); Console.WriteLine($" Mlaunch:\t{info.MlaunchVersion} - {info.MlaunchPath}"); Console.WriteLine(); Console.WriteLine("Installed Simulators:"); if (info.Simulators.Any()) { var maxLength = info.Simulators.Select(s => s.Name.Length).Max(); foreach (var sim in info.Simulators) { var uuid = Arguments.ShowSimulatorsUUID ? $"{sim.UDID} " : string.Empty; Console.WriteLine($" {uuid}{sim.Name.PadRight(maxLength)} {sim.OSVersion,-13} {sim.Type}"); } } else { Console.WriteLine(" none"); } Console.WriteLine(); Console.WriteLine("Connected Devices:"); if (info.Devices.Any()) { var maxLength = info.Devices.Select(s => s.Name.Length).Max(); foreach (var dev in info.Devices) { var uuid = Arguments.ShowDevicesUUID ? $" {dev.UDID} " : ""; var notPaired = dev.IsPaired ? "" : "(not paired) "; Console.WriteLine($" {notPaired}{dev.Name.PadRight(maxLength)}{uuid} {dev.OSVersion,-13} {dev.Type}"); } } else { Console.WriteLine(" none"); } } protected override async Task InvokeInternal(Extensions.Logging.ILogger logger) { var processManager = new MlaunchProcessManager(xcodeRoot: Arguments.XcodeRoot, mlaunchPath: Arguments.MlaunchPath); var deviceLoader = new HardwareDeviceLoader(processManager); var simulatorLoader = new SimulatorLoader(processManager); var log = new MemoryLog(); // do we really want to log this? var mlaunchLog = new MemoryLog { Timestamp = false }; ProcessExecutionResult result; try { result = await processManager.ExecuteCommandAsync(new MlaunchArguments(new MlaunchVersionArgument()), new NullLog(), mlaunchLog, new NullLog(), TimeSpan.FromSeconds(10)); } catch (Exception e) { logger.LogError($"Failed to get mlaunch version info:{Environment.NewLine}{e}"); return ExitCode.GENERAL_FAILURE; } if (!result.Succeeded) { logger.LogError($"Failed to get mlaunch version info:{Environment.NewLine}{mlaunchLog}"); return ExitCode.GENERAL_FAILURE; } // build the required data, then depending on the format print out var info = new SystemInfo( machineName: Environment.MachineName, oSName: "Mac OS X", oSVersion: Darwin.GetVersion() ?? "", oSPlatform: "Darwin", xcodePath: processManager.XcodeRoot, xcodeVersion: processManager.XcodeVersion.ToString(), mlaunchPath: processManager.MlaunchPath, mlaunchVersion: mlaunchLog.ToString().Trim()); try { await simulatorLoader.LoadDevices(log); } catch (Exception e) { logger.LogError($"Failed to load simulators:{Environment.NewLine}{e}"); logger.LogInformation($"Execution log:{Environment.NewLine}{log}"); return ExitCode.GENERAL_FAILURE; } foreach (var sim in simulatorLoader.AvailableDevices) { info.Simulators.Add(new DeviceInfo( name: sim.Name, uDID: sim.UDID, type: sim.SimDeviceType.Remove(0, SimulatorPrefix.Length).Replace('-', ' '), oSVersion: sim.OSVersion)); } try { await deviceLoader.LoadDevices(log, includeWirelessDevices: Arguments.IncludeWireless); } catch (Exception e) { logger.LogError($"Failed to load connected devices:{Environment.NewLine}{e}"); logger.LogInformation($"Execution log:{Environment.NewLine}{log}"); return ExitCode.GENERAL_FAILURE; } foreach (var dev in deviceLoader.ConnectedDevices) { info.Devices.Add(new DeviceInfo( name: dev.Name, uDID: dev.DeviceIdentifier, type: $"{dev.DeviceClass} {dev.DevicePlatform}", oSVersion: dev.OSVersion, isPaired: dev.IsPaired)); } if (Arguments.UseJson) { await AsJson(info); } else { AsText(info); } return ExitCode.SUCCESS; } private class MlaunchVersionArgument : OptionArgument { public MlaunchVersionArgument() : base("version") { } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleTestCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Apple; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.CLI.Resources; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; /// /// Command which executes a given, already-packaged iOS application, waits on it and returns status based on the outcome. /// internal class AppleTestCommand : AppleAppCommand { protected override string CommandUsage { get; } = Strings.Apple_Test_Usage; protected override string CommandDescription { get; } = Strings.Apple_Test_Description; protected override AppleTestCommandArguments Arguments { get; } = new(); public AppleTestCommand(IServiceCollection services) : base("test", false, services, Strings.Apple_Test_Description) { } protected override Task InvokeInternal(ServiceProvider serviceProvider, CancellationToken cancellationToken) { IReadOnlyCollection<(string, string?)> envVars = Arguments.EnvironmentalVariables.Value; if (Arguments.EnableCoverage) { // Inject coverage env vars so the test runner on the device enables coverage. // Use Documents/coverage.cobertura.xml — the same directory where test results go, // which the orchestrator already knows how to pull from the app container. var coverageVars = new List<(string, string?)>(envVars) { ("NUNIT_ENABLE_COVERAGE", "true"), ("NUNIT_COVERAGE_OUTPUT_PATH", "coverage.cobertura.xml"), }; envVars = coverageVars; } return serviceProvider.GetRequiredService() .OrchestrateTest( Arguments.AppBundlePath, Arguments.Target, Arguments.DeviceName, Arguments.Timeout, Arguments.LaunchTimeout, Arguments.CommunicationChannel, Arguments.XmlResultJargon, Arguments.SingleMethodFilters.Value, Arguments.ClassMethodFilters.Value, includeWirelessDevices: Arguments.IncludeWireless, resetSimulator: Arguments.ResetSimulator, enableLldb: Arguments.EnableLldb, signalAppEnd: Arguments.SignalAppEnd, envVars, PassThroughArguments, cancellationToken); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleUninstallCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Apple; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; internal class AppleUninstallCommand : AppleAppCommand { private const string CommandHelp = "Uninstalls a given iOS/tvOS/watchOS/xrOS/MacCatalyst application bundle from a target device/simulator"; protected override AppleUninstallCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "apple uninstall --app=... --output-directory=... --target=... [OPTIONS] [-- [RUNTIME ARGUMENTS]]"; protected override string CommandDescription { get; } = CommandHelp; public AppleUninstallCommand(IServiceCollection services) : base("uninstall", false, services, CommandHelp) { } protected override Task InvokeInternal(ServiceProvider serviceProvider, CancellationToken cancellationToken) => serviceProvider.GetRequiredService() .OrchestrateAppUninstall( Arguments.BundleIdentifier, Arguments.Target, Arguments.DeviceName, Arguments.Timeout, Arguments.IncludeWireless, resetSimulator: false, enableLldb: false, cancellationToken); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/FindCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Linq; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple.Simulators; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple.Simulators; internal class FindCommand : SimulatorsCommand { private const string CommandName = "find"; private const string CommandHelp = "Finds whether given simulators are installed and outputs list of missing ones (returns 0 when none missing)"; protected override string CommandUsage => CommandName + " [OPTIONS] [SIMULATOR] [SIMULATOR] .."; protected override string CommandDescription => CommandHelp + Environment.NewLine + Environment.NewLine + SimulatorHelpString; protected override FindCommandArguments Arguments { get; } = new(); public FindCommand() : base(CommandName, true, CommandHelp) { } protected override async Task InvokeInternal(ILogger logger) { Logger = logger; var searchedSimulators = ParseSimulatorIds(); var simulators = await GetAvailableSimulators(); var exitCode = ExitCode.SUCCESS; var unknownSimulators = searchedSimulators.Where(identifier => !simulators.Any(sim => sim.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase))); if (unknownSimulators.Any()) { // This output is actually matched in some tools, so please don't change var message = "Unknown simulators: " + string.Join(", ", unknownSimulators); if (Arguments.Verbosity == LogLevel.Debug) { Logger.LogDebug(message); } else { // For parsing Console.WriteLine(message); } return ExitCode.DEVICE_NOT_FOUND; } // We output a list of simulators that were supplied and not installed foreach (var simulator in simulators) { var installedVersion = await IsInstalled(simulator); if (installedVersion == null && searchedSimulators.Any(identifier => simulator.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase))) { if (Arguments.Verbosity == LogLevel.Debug) { Logger.LogDebug($"The simulator '{simulator.Name}' is not installed"); } else { // For parsing Console.WriteLine(simulator.Identifier); } exitCode = ExitCode.DEVICE_NOT_FOUND; continue; } if (installedVersion >= Version.Parse(simulator.Version)) { Logger.LogDebug($"The simulator {simulator.Version} is installed ({simulator.Version})"); } else { Logger.LogDebug($"The simulator {simulator.Name} is installed, but an update is available ({simulator.Version})."); } } return exitCode; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/InstallCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Xml; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple.Simulators; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple.Simulators; internal class InstallCommand : SimulatorsCommand { private const string CommandName = "install"; private const string CommandHelp = "Installs given simulators"; private static readonly HttpClient s_client = new(new HttpClientHandler { CheckCertificateRevocationList = true }); protected override string CommandUsage => CommandName + " [OPTIONS] [SIMULATOR] [SIMULATOR] .."; protected override string CommandDescription => CommandHelp + Environment.NewLine + Environment.NewLine + SimulatorHelpString; protected override InstallCommandArguments Arguments { get; } = new(); public InstallCommand() : base(CommandName, true, CommandHelp) { } protected override async Task InvokeInternal(ILogger logger) { Logger = logger; var simulatorIds = ParseSimulatorIds(); var simulators = await GetAvailableSimulators(); var exitCode = ExitCode.SUCCESS; if (!simulatorIds.Any()) { logger.LogError("You have to specify at least one simulator to install!"); return ExitCode.INVALID_ARGUMENTS; } var unknownSimulators = simulatorIds.Where(identifier => !simulators.Any(sim => sim.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase))); if (unknownSimulators.Any()) { Logger.LogError("Unknown simulators: " + string.Join(", ", unknownSimulators)); return ExitCode.DEVICE_NOT_FOUND; } foreach (var simulator in simulators) { if (!simulatorIds.Any(identifier => simulator.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase))) { Logger.LogDebug($"Skipping '{simulator.Identifier}'"); continue; } var installedVersion = await IsInstalled(simulator); var shouldInstall = false; if (installedVersion == null) { Logger.LogInformation($"The simulator '{simulator.Name}' is missing and will be installed"); shouldInstall = true; } else { if (installedVersion >= Version.Parse(simulator.Version)) { if (Arguments.Force) { Logger.LogInformation($"The simulator '{simulator.Name}' is installed but --force was supplied so reinstalling"); shouldInstall = true; } else { Logger.LogInformation($"The simulator '{simulator.Name}' is already installed ({simulator.Version})"); } } else { Logger.LogInformation($"The simulator '{simulator.Name}' is installed, but an update is available ({simulator.Version})."); } } if (shouldInstall) { Logger.LogInformation($"Installing '{simulator.Name}' ({simulator.Version})..."); try { if (await Install(simulator)) { Logger.LogInformation($"Installed '{simulator.Name}' successfully"); } else { Logger.LogError($"Failed to install '{simulator.Name}'"); exitCode = ExitCode.GENERAL_FAILURE; } } catch (Exception e) { Logger.LogError($"Failed to install '{simulator.Name}':{Environment.NewLine}{e}"); exitCode = ExitCode.GENERAL_FAILURE; } } } return exitCode; } private async Task Install(Simulator simulator) { var xcodeVersion = await GetXcodeVersion(); if (CanXcodeDownloadSimulatorsUsingCLI(xcodeVersion) && CanSimulatorBeInstalledUsingXcodeCLI(simulator)) { Logger.LogInformation($"Downloading and installing simulator: {simulator.Name} through xcodebuild with Xcode: {xcodeVersion}"); var (succeeded, stdout) = await ExecuteCommand("xcodebuild", TimeSpan.FromMinutes(15), "-downloadPlatform", simulator.Platform, "-buildVersion", simulator.BuildUpdate, "-verbose"); if (!succeeded) { Logger.LogError($"Download and installation failed through xcodebuild for simulator: {simulator.Name} with Xcode: {xcodeVersion}!" + Environment.NewLine + stdout); return false; } else { Logger.LogDebug(stdout); return true; } } // Xcode 16.0 trying to install new simulators (e.g. iOS 18) path else if (xcodeVersion.Major == 16 && xcodeVersion.Minor == 0 && simulator.Source is null) { Logger.LogWarning($"Xcode {xcodeVersion} does not support selecting simulator versions using CLI. It will always default to the latest version. Consider upgrading to Xcode 16.1 or later."); Logger.LogInformation($"Downloading and installing the latest {simulator.Platform} simulator through xcodebuild with Xcode: {xcodeVersion}"); var (succeeded, stdout) = await ExecuteCommand("xcodebuild", TimeSpan.FromMinutes(15), "-downloadPlatform", simulator.Platform, "-verbose"); if (!succeeded) { Logger.LogError($"Download and installation failed through xcodebuild for the latest {simulator.Platform} simulator with Xcode: {xcodeVersion}!" + Environment.NewLine + stdout); return false; } else { Logger.LogDebug(stdout); return true; } } else if (simulator.Source is not null) { var filename = Path.GetFileName(simulator.Source); var downloadPath = Path.Combine(TempDirectory, filename); var download = true; if (!File.Exists(downloadPath)) { Logger.LogInformation( $"Downloading '{simulator.Source}' to '{downloadPath}' " + $"(size: {simulator.FileSize} bytes = {simulator.FileSize / 1024.0 / 1024.0:N2} MB)..."); } else if (new FileInfo(downloadPath).Length != simulator.FileSize) { Logger.LogInformation( $"Downloading '{simulator.Source}' to '{downloadPath}' because the existing file's " + $"size {new FileInfo(downloadPath).Length} does not match the expected size {simulator.FileSize}..."); } else { download = false; } if (download) { await DownloadSimulator(simulator, downloadPath); } return await InstallSimulator(simulator, downloadPath, filename); } else { throw new Exception($"Cannot download simulator: {simulator.Name} from source nor through Xcode: {xcodeVersion}"); } } // https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes private static bool CanSimulatorBeInstalledUsingXcodeCLI(Simulator simulator) { var simulatorVersion = Version.Parse(simulator.Version); return simulator.Platform switch { "iOS" => simulatorVersion.Major >= 16, "tvOS" => simulatorVersion.Major >= 16, "watchOS" => simulatorVersion.Major >= 9, "visionOS" => simulatorVersion.Major >= 1, _ => false, }; } private static bool CanXcodeDownloadSimulatorsUsingCLI(Version xcodeVersion) { // -buildVersion is only supported in Xcode 16.1 and later return (xcodeVersion.Major == 16 && xcodeVersion.Minor >= 1) || xcodeVersion.Major > 16; } private async Task DownloadSimulator(Simulator simulator, string downloadPath) { var watch = Stopwatch.StartNew(); var httpClient = s_client; if (simulator.IsDmgFormat) { CookieContainer cookies = new(); // we need to first get the cookie from ADC var path = Uri.TryCreate(simulator.Source, UriKind.Absolute, out var simulatorUri) ? simulatorUri.AbsolutePath : throw new InvalidOperationException($"Could not parse the simulator URL: {simulator.Source}"); httpClient = new HttpClient(new HttpClientHandler() { CookieContainer = cookies, CheckCertificateRevocationList = true }); httpClient.DefaultRequestHeaders.Add("User-Agent", $"dotnet/xharness {XHarnessVersionCommand.GetAssemblyVersion().ProductVersion}"); // otherwise we get a 401 Unauthorized var adcDownloadUrl = $"https://developerservices2.apple.com/services/download?path={path}"; using (var response = await httpClient.GetAsync(adcDownloadUrl, HttpCompletionOption.ResponseHeadersRead)) { response.EnsureSuccessStatusCode(); foreach (Cookie cookie in cookies.GetAllCookies()) { if (cookie.Name == "ADCDownloadAuth") { // transfer this cookie to the simulator download URI cookies.Add(simulatorUri, new Cookie(cookie.Name, cookie.Value)); } } } } using (var response = await httpClient.GetAsync(simulator.Source, HttpCompletionOption.ResponseHeadersRead)) { response.EnsureSuccessStatusCode(); using var fileStream = File.Create(downloadPath); if (Arguments.HideProgress) { await response.Content.CopyToAsync(fileStream); } else { var progressMessage = $"Starting the download.."; Console.Write(progressMessage); void ShowProgress(long totalBytesDownloaded) { var previousLength = progressMessage.Length; progressMessage = $"[{watch.Elapsed:hh\\:mm\\:ss}] {totalBytesDownloaded / 1024.0 / 1024.0:N2} / {simulator.FileSize / 1024.0 / 1024.0:N2} MB\t\t{(int)(100 * totalBytesDownloaded / simulator.FileSize),3}%"; Console.Write("\r" + progressMessage.PadRight(previousLength)); } var totalBytesDownloaded = 0L; var buffer = new byte[8192]; var lastUpdate = DateTime.Now; var updateFrequency = TimeSpan.FromMilliseconds(400); using var responseStream = await response.Content.ReadAsStreamAsync(); while (true) { var bytesRead = await responseStream.ReadAsync(buffer); if (bytesRead == 0) { Console.Write("\r" + " ".PadRight(progressMessage.Length) + "\r"); break; } await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead)); totalBytesDownloaded += bytesRead; if (DateTime.Now - lastUpdate > updateFrequency) { ShowProgress(totalBytesDownloaded); lastUpdate = DateTime.Now; } } } } watch.Stop(); var size = new FileInfo(downloadPath).Length; Logger.LogInformation($"Downloaded {size / 1024.0 / 1024.0:N1} MB in {watch.Elapsed:hh\\:mm\\:ss}"); } private async Task InstallSimulator(Simulator simulator, string downloadPath, string filename) { if (simulator.IsDmgFormat) { Logger.LogInformation($"Installing simulator '{downloadPath}' using simctl..."); var (succeeded, stdout) = await ExecuteCommand("xcrun", TimeSpan.FromMinutes(15), "simctl", "runtime", "add", downloadPath); if (!succeeded) { Logger.LogError("Installation failure!" + Environment.NewLine + stdout); return false; } File.Delete(downloadPath); return true; } else { var mount_point = Path.Combine(TempDirectory, filename + "-mount"); Directory.CreateDirectory(mount_point); try { Logger.LogInformation($"Mounting '{downloadPath}' into '{mount_point}'..."); var (succeeded, stdout) = await ExecuteCommand("hdiutil", TimeSpan.FromMinutes(1), "attach", downloadPath, "-mountpoint", mount_point, "-quiet", "-nobrowse"); if (!succeeded) { Logger.LogError("Mount failure!" + Environment.NewLine + stdout); return false; } try { var packages = Directory.GetFiles(mount_point, "*.pkg"); if (packages.Length == 0) { Logger.LogError("Found no *.pkg files in the dmg."); return false; } else if (packages.Length > 1) { Logger.LogError("Found more than one *.pkg file in the dmg:\n\t{0}", string.Join("\n\t", packages)); return false; } // According to the package manifest, the package's install location is /. // That's obviously not where it's installed, but I have no idea how Apple does it // So instead decompress the package, modify the package manifest, re-create the package, and then install it. var expanded_path = Path.Combine(TempDirectory + "-expanded-pkg"); if (Directory.Exists(expanded_path)) { Directory.Delete(expanded_path, true); } Logger.LogInformation($"Expanding '{packages[0]}' into '{expanded_path}'..."); (succeeded, stdout) = await ExecuteCommand("pkgutil", TimeSpan.FromMinutes(1), "--expand", packages[0], expanded_path); if (!succeeded) { Logger.LogError($"Failed to expand {packages[0]}:" + Environment.NewLine + stdout); return false; } try { var packageInfoPath = Path.Combine(expanded_path, "PackageInfo"); var packageInfoDoc = new XmlDocument(); packageInfoDoc.Load(packageInfoPath); // Add the install-location attribute to the pkg-info node var attr = packageInfoDoc.CreateAttribute("install-location"); attr.Value = simulator.InstallPrefix; packageInfoDoc.SelectSingleNode("/pkg-info")?.Attributes?.Append(attr); packageInfoDoc.Save(packageInfoPath); var fixed_path = Path.Combine(Path.GetDirectoryName(downloadPath)!, Path.GetFileNameWithoutExtension(downloadPath) + "-fixed.pkg"); if (File.Exists(fixed_path)) { File.Delete(fixed_path); } try { Logger.LogInformation($"Creating fixed package '{fixed_path}' from '{expanded_path}'..."); (succeeded, stdout) = await ExecuteCommand("pkgutil", TimeSpan.FromMinutes(2), "--flatten", expanded_path, fixed_path); if (!succeeded) { Logger.LogError("Failed to create fixed package:" + Environment.NewLine + stdout); return false; } Logger.LogInformation($"Installing '{fixed_path}'..."); (succeeded, stdout) = await ExecuteCommand("sudo", TimeSpan.FromMinutes(15), "installer", "-pkg", fixed_path, "-target", "/", "-verbose", "-dumplog"); if (!succeeded) { Logger.LogError("Failed to install package:" + Environment.NewLine + stdout); return false; } } finally { if (File.Exists(fixed_path)) { File.Delete(fixed_path); } } } finally { Directory.Delete(expanded_path, true); } } finally { await ExecuteCommand("hdiutil", TimeSpan.FromMinutes(5), "detach", mount_point, "-quiet"); } } finally { Directory.Delete(mount_point, true); } File.Delete(downloadPath); return true; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/ListCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Text; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple.Simulators; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple.Simulators; internal class ListCommand : SimulatorsCommand { private const string CommandName = "list"; private const string CommandHelp = "Lists available simulators"; protected override string CommandUsage => CommandName; protected override string CommandDescription => CommandHelp; protected override ListCommandArguments Arguments { get; } = new(); public ListCommand() : base(CommandName, false, CommandHelp) { } protected override async Task InvokeInternal(ILogger logger) { Logger = logger; var simulators = await GetAvailableSimulators(); foreach (var simulator in simulators) { var output = new StringBuilder(); output.AppendLine(simulator.Name); output.Append($" Version: {simulator.Version}"); string? installStatus = null; var installedVersion = await IsInstalled(simulator); if (installedVersion == null) { if (Arguments.ListInstalledOnly) { Logger.LogDebug($"The simulator '{simulator.Name}' is not installed"); continue; } installStatus = "not installed"; } else { if (installedVersion >= Version.Parse(simulator.Version)) { if (!Arguments.ListInstalledOnly) { installStatus = "installed"; } } else { output.AppendLine(); installStatus = $"an earlier version is installed: {installedVersion}"; } } output.AppendLine($" ({installStatus})"); output.AppendLine($" Source: {simulator.Source}"); output.AppendLine($" Identifier: {simulator.Identifier}"); output.AppendLine($" InstallPrefix: {simulator.InstallPrefix}"); Logger.LogInformation(output.ToString()); } return ExitCode.SUCCESS; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/Simulator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple.Simulators; internal class Simulator { public string Name { get; } public string Platform { get; } public string Identifier { get; } public string Version { get; } public string? Source { get; } public string InstallPrefix { get; } public long FileSize { get; } public bool IsDmgFormat { get; } public string BuildUpdate { get; } public Simulator(string name, string platform, string identifier, string version, string? source, string installPrefix, long fileSize, string buildUpdate) { Name = name; Platform = platform; Identifier = identifier; Version = version; Source = source; InstallPrefix = installPrefix; FileSize = fileSize; IsDmgFormat = Identifier.StartsWith("com.apple.dmg.", StringComparison.OrdinalIgnoreCase); BuildUpdate = buildUpdate; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/SimulatorsCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Runtime.Serialization; using System.Text.Json; using System.Threading.Tasks; using System.Xml; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple.Simulators; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple.Simulators; internal abstract class SimulatorsCommand : XHarnessCommand { private const string MAJOR_VERSION_PLACEHOLDER = "DOWNLOADABLE_VERSION_MAJOR"; private const string MINOR_VERSION_PLACEHOLDER = "DOWNLOADABLE_VERSION_MINOR"; private const string VERSION_PLACEHOLDER = "DOWNLOADABLE_VERSION"; private const string IDENTIFIER_PLACEHOLDER = "DOWNLOADABLE_IDENTIFIER"; protected const string SimulatorHelpString = "Accepts a list of simulator IDs to install. The ID can be a fully qualified string, " + "e.g. com.apple.pkg.AppleTVSimulatorSDK14_2 or you can use the format in which you specify " + "apple targets for XHarness tests (ios-simulator, tvos-simulator, watchos-simulator, xros-simulator)."; private static readonly HttpClient s_client = new(new HttpClientHandler { CheckCertificateRevocationList = true }); private readonly MacOSProcessManager _processManager = new(); private Version? _xcodeVersion; private string? _xcodeUuid; protected ILogger Logger { get; set; } = null!; protected SimulatorsCommand(string name, bool allowsExtraArgs, string help) : base(TargetPlatform.Apple, name, allowsExtraArgs, new ServiceCollection(), help) { } protected static string TempDirectory { get { var path = Path.Combine(Path.GetTempPath(), "simulator-installer"); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } return path; } } protected async Task<(bool Succeeded, string Stdout)> ExecuteCommand( string filename, TimeSpan? timeout = null, params string[] arguments) { var stdoutLog = new MemoryLog() { Timestamp = false }; var stderrLog = new MemoryLog() { Timestamp = false }; var result = await _processManager.ExecuteCommandAsync( filename, arguments, new CallbackLog(m => Logger.LogDebug(m)), stdoutLog, stderrLog, timeout ?? TimeSpan.FromSeconds(30)); var stderr = stderrLog.ToString(); if (stderr.Length > 0) { Logger.LogDebug("Error output:" + Environment.NewLine + stderr); } return (result.Succeeded, stdoutLog.ToString()); } protected async Task> GetAvailableSimulators() { var doc = new XmlDocument(); doc.LoadXml(await GetSimulatorIndexXml() ?? throw new FailedToGetIndexException()); var simulators = new List(); var downloadables = doc.SelectNodes("//plist/dict/key[text()='downloadables']/following-sibling::array[1]/dict"); foreach (XmlNode? downloadable in downloadables!) { if (downloadable == null) { continue; } var nameNode = downloadable.SelectSingleNode("key[text()='name']/following-sibling::string") ?? throw new Exception("Name node not found"); var versionNode = downloadable.SelectSingleNode("key[text()='version']/following-sibling::string") ?? throw new Exception("Version node not found"); var identifierNode = downloadable.SelectSingleNode("key[text()='identifier']/following-sibling::string") ?? throw new Exception("Identifier node not found"); var sourceNode = downloadable.SelectSingleNode("key[text()='source']/following-sibling::string"); var fileSizeNode = downloadable.SelectSingleNode("key[text()='fileSize']/following-sibling::integer|key[text()='fileSize']/following-sibling::real"); var installPrefixNode = downloadable.SelectSingleNode("key[text()='userInfo']/following-sibling::dict/key[text()='InstallPrefix']/following-sibling::string"); var buildUpdateNode = downloadable.SelectSingleNode("key[text()='simulatorVersion']/following-sibling::dict[1]/key[text()='buildUpdate']/following-sibling::string[1]"); var version = versionNode.InnerText; var versions = version.Split('.'); var versionMajor = versions[0]; var versionMinor = versions[1]; var dict = new Dictionary() { { MAJOR_VERSION_PLACEHOLDER, versionMajor }, { MINOR_VERSION_PLACEHOLDER, versionMinor }, { VERSION_PLACEHOLDER, version }, }; var identifier = ReplaceStringUsingKey(identifierNode.InnerText, dict); dict.Add(IDENTIFIER_PLACEHOLDER, identifier); _ = double.TryParse(fileSizeNode?.InnerText, out var parsedFileSize); var name = ReplaceStringUsingKey(nameNode.InnerText, dict); var installPrefix = ReplaceStringUsingKey(installPrefixNode?.InnerText, dict); if (installPrefix is null) { // newer simulators aren't installed anymore, provide a dummy value here var simRuntimeName = name.Replace(" Simulator", ".simruntime"); installPrefix = $"/Library/Developer/CoreSimulator/Profiles/Runtimes/{simRuntimeName}"; } var platform = name.Split(' ').FirstOrDefault(); if (platform is null) { Logger.LogWarning($"Platform name could not be parsed from simulator name: '{nameNode.InnerText}' version: '{versionNode.InnerText}' identifier: '{identifierNode.InnerText}' skipping..."); continue; } var source = ReplaceStringUsingKey(sourceNode?.InnerText, dict); string? buildUpdate = buildUpdateNode?.InnerText; if (buildUpdate is null) { Logger.LogWarning($"Simulator with name: '{nameNode.InnerText}' version: '{versionNode.InnerText}' identifier: '{identifierNode.InnerText}' has no buildUpdate, skipping..."); continue; } simulators.Add(new Simulator( name: name, platform: platform, identifier: ReplaceStringUsingKey(identifierNode.InnerText, dict), version: versionNode.InnerText, source: source, installPrefix: installPrefix, fileSize: (long)parsedFileSize, buildUpdate: buildUpdate )); } return simulators; } [return: NotNullIfNotNull("value")] static string? ReplaceStringUsingKey(string? value, Dictionary replacements) { if (value is null) return null; foreach (var kvp in replacements) { value = value.Replace($"$({kvp.Key})", kvp.Value); } return value; } protected async Task IsInstalled(Simulator simulator) { var xcodeVersion = await GetXcodeVersion(); bool isXcode14 = xcodeVersion.Major >= 14; if (simulator.Identifier.StartsWith("com.apple.dmg.") && isXcode14) { var (succeeded, json) = await ExecuteCommand($"xcrun", TimeSpan.FromMinutes(1), "simctl", "runtime", "list", "-j"); if (!succeeded) { return null; } Logger.LogDebug($"Listing runtime disk images via returned: {json}"); string simulatorRuntime = ""; string simulatorVersion = ""; if (simulator.Identifier.StartsWith("com.apple.dmg.iPhoneSimulatorSDK")) { simulatorRuntime = "com.apple.CoreSimulator.SimRuntime.iOS-"; simulatorVersion = simulator.Identifier.Substring("com.apple.dmg.iPhoneSimulatorSDK".Length); } else if (simulator.Identifier.StartsWith("com.apple.dmg.AppleTVSimulatorSDK")) { simulatorRuntime = "com.apple.CoreSimulator.SimRuntime.tvOS-"; simulatorVersion = simulator.Identifier.Substring("com.apple.dmg.AppleTVSimulatorSDK".Length); } else if (simulator.Identifier.StartsWith("com.apple.dmg.WatchSimulatorSDK")) { simulatorRuntime = "com.apple.CoreSimulator.SimRuntime.watchOS-"; simulatorVersion = simulator.Identifier.Substring("com.apple.dmg.WatchSimulatorSDK".Length); } else if (simulator.Identifier.StartsWith("com.apple.dmg.xrSimulatorSDK")) { simulatorRuntime = "com.apple.CoreSimulator.SimRuntime.xrOS-"; simulatorVersion = simulator.Identifier.Substring("com.apple.dmg.xrSimulatorSDK".Length); } else { Logger.LogWarning($"Unknown simulator type: {simulator.Identifier}"); } // trim away any beta suffix string simulatorBetaVersion = ""; if (simulatorVersion.Contains("_b")) { simulatorBetaVersion = simulatorVersion.Substring(simulatorVersion.LastIndexOf("_b") + "_b".Length); simulatorVersion = simulatorVersion.Substring(0, simulatorVersion.LastIndexOf("_b")); } var runtimeIdentifier = simulatorRuntime + simulatorVersion.Replace('_', '-'); var simulators = JsonDocument.Parse(json); foreach (JsonProperty sim in simulators.RootElement.EnumerateObject()) { // Skip entries that don't have a runtimeIdentifier property (e.g., unusable simulators) if (!sim.Value.TryGetProperty("runtimeIdentifier", out var runtimeIdProperty)) { continue; } if (runtimeIdProperty.GetString() == runtimeIdentifier) { // Also check if version property exists if (!sim.Value.TryGetProperty("version", out var versionProperty)) { return null; } var version = versionProperty.GetString(); if (version == null) return null; // make sure we have a proper major.minor.build.revision version // and if we have a beta version, add it to the version as the revision parameter if (version.Count(c => c == '.') == 1) version += simulatorBetaVersion == "" ? ".0.0" : $".0.{simulatorBetaVersion}"; else if (version.Count(c => c == '.') == 2) version += simulatorBetaVersion == "" ? ".0" : $".{simulatorBetaVersion}"; // TODO: the version returned by simctl and index2.dvtdownloadableindex for dmg packages is not a unique version like for pkg but just major.minor.0.0, // we could use the "build" key from simctl to compare with the "buildUpdate" in the index2.dvtdownloadableindex return Version.TryParse(version, out var parsedVersion) ? parsedVersion : null; } } return null; } else if (simulator.Identifier.StartsWith("com.apple.pkg.")) { var (succeeded, pkgInfo) = await ExecuteCommand($"pkgutil", TimeSpan.FromMinutes(1), "--pkg-info", simulator.Identifier); if (!succeeded) { return null; } var lines = pkgInfo.Split('\n'); var version = lines.First(v => v.StartsWith("version: ", StringComparison.Ordinal)).Substring("version: ".Length); return Version.Parse(version); } return null; } protected IEnumerable ParseSimulatorIds() { var simulators = new List(); foreach (string argument in ExtraArguments) { if (argument.StartsWith("com.apple.pkg.") || argument.StartsWith("com.apple.dmg.")) { simulators.Add(argument); continue; } TestTargetOs target; try { target = argument.ParseAsAppRunnerTargetOs(); } catch (ArgumentOutOfRangeException e) { throw new ArgumentException( $"Failed to parse simulator '{argument}'. Available values are ios-simulator, tvos-simulator, watchos-simulator and xros-simulator." + Environment.NewLine + Environment.NewLine + "You need to also specify the version. Example: ios-simulator_13.4", e); } if (string.IsNullOrEmpty(target.OSVersion)) { throw new ArgumentException($"Failed to parse simulator '{argument}'. " + $"You need to specify the exact version. Example: ios-simulator_13.4"); } var testTargetVersion = Version.Parse(target.OSVersion); (string simulatorName, string simulatorFormat) = target.Platform switch { TestTarget.Simulator_iOS64 => ("iPhone", testTargetVersion.Major >= 16 ? "dmg" : "pkg"), TestTarget.Simulator_tvOS => ("AppleTV", testTargetVersion.Major >= 16 ? "dmg" : "pkg"), TestTarget.Simulator_watchOS => ("Watch", testTargetVersion.Major >= 9 ? "dmg" : "pkg"), TestTarget.Simulator_xrOS => ("xrOS", "dmg"), _ => throw new ArgumentException($"Failed to parse simulator '{argument}'. " + "Available values are ios-simulator, tvos-simulator, watchos-simulator and xros-simulator." + Environment.NewLine + Environment.NewLine + "You need to also specify the version. Example: ios-simulator_13.4"), }; // e.g. com.apple.pkg.AppleTVSimulatorSDK14_3 simulators.Add($"com.apple.{simulatorFormat}.{simulatorName}SimulatorSDK{target.OSVersion.Replace(".", "_")}"); } return simulators; } private async Task GetSimulatorIndexXml() { var xcodeVersion = await GetXcodeVersion(); string indexUrl, indexName; if (xcodeVersion.Major >= 14) { /* * The following url was found while debugging Xcode, the "index2" part is actually hardcoded: * * DVTFoundation`-[DVTDownloadableIndexSource identifier]: * 0x103db478d <+0>: pushq %rbp * 0x103db478e <+1>: movq %rsp, %rbp * 0x103db4791 <+4>: leaq 0x53f008(%rip), %rax ; @"index2" * 0x103db4798 <+11>: popq %rbp * 0x103db4799 <+12>: retq * */ indexName = $"index-{xcodeVersion}.dvtdownloadableindex"; indexUrl = "https://devimages-cdn.apple.com/downloads/xcode/simulators/index2.dvtdownloadableindex"; } else { var xcodeUuid = await GetXcodeUuid(); indexName = $"index-{xcodeVersion}-{xcodeUuid}.dvtdownloadableindex"; indexUrl = $"https://devimages-cdn.apple.com/downloads/xcode/simulators/{indexName}"; } var tmpfile = Path.Combine(TempDirectory, indexName); if (!File.Exists(tmpfile)) { if (!await DownloadFile(indexUrl, tmpfile)) return null; } else { Logger.LogInformation($"File '{tmpfile}' already exists, skipped download"); } var (succeeded, xmlResult) = await ExecuteCommand("plutil", TimeSpan.FromSeconds(30), "-convert", "xml1", "-o", "-", tmpfile); if (!succeeded) { return null; } return xmlResult; } private async Task DownloadFile(string url, string destinationPath) { try { Logger.LogInformation($"Downloading {url}..."); var downloadTask = s_client.GetStreamAsync(url); using var fileStream = new FileStream(destinationPath, FileMode.Create); using var bodyStream = await downloadTask; await bodyStream.CopyToAsync(fileStream); return true; } catch (HttpRequestException e) { // 403 means 404 if (e.StatusCode == HttpStatusCode.Forbidden) { // Apple's servers return a 403 if the file doesn't exist, which can be quite confusing, so show a better error. Logger.LogWarning($"Failed to download {url}: Not found"); } else { Logger.LogWarning($"Failed to download {url}: {e}"); } } return false; } protected async Task GetXcodeVersion() { if (_xcodeVersion is not null) { return _xcodeVersion; } string xcodeRoot = Arguments.XcodeRoot.Value ?? new MacOSProcessManager().XcodeRoot; var plistPath = Path.Combine(xcodeRoot, "Contents", "Info.plist"); var (succeeded, xcodeVersion) = await ExecuteCommand("/usr/libexec/PlistBuddy", TimeSpan.FromSeconds(5), "-c", "Print :DTXcode", plistPath); if (!succeeded) { throw new Exception("Failed to detect Xcode version!"); } xcodeVersion = xcodeVersion.Trim(); // the first two digits of DTXcode are the major version, then minor and revision so e.g. 1520 would translate to 15.2.0 xcodeVersion = xcodeVersion.Insert(xcodeVersion.Length - 2, "."); xcodeVersion = xcodeVersion.Insert(xcodeVersion.Length - 1, "."); _xcodeVersion = Version.Parse(xcodeVersion); return _xcodeVersion; } private async Task GetXcodeUuid() { if (_xcodeUuid is not null) { return _xcodeUuid; } string xcodeRoot = Arguments.XcodeRoot.Value ?? new MacOSProcessManager().XcodeRoot; var plistPath = Path.Combine(xcodeRoot, "Contents", "Info.plist"); var (succeeded, xcodeUuid) = await ExecuteCommand("/usr/libexec/PlistBuddy", TimeSpan.FromSeconds(5), "-c", "Print :DVTPlugInCompatibilityUUID", plistPath); if (!succeeded) { throw new Exception("Failed to detect Xcode UUID! This is only available on Xcode < 15.3."); } _xcodeUuid = xcodeUuid.Trim(); return _xcodeUuid; } [Serializable] protected class FailedToGetIndexException : Exception { public FailedToGetIndexException() : this("Failed to download the list of available simulators from Apple") { } public FailedToGetIndexException(string? message) : base(message) { } public FailedToGetIndexException(string? message, Exception? innerException) : base(message, innerException) { } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/SimulatorsCommandSet.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Mono.Options; namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple.Simulators; /// /// These commands allow management of Xcode iOS/WatchOS/tvOS Simulators on MacOS. /// Originally taken from: https://github.com/xamarin/xamarin-macios/blob/master/tools/siminstaller /// public class SimulatorsCommandSet : CommandSet { public SimulatorsCommandSet() : base("simulators") { Add(new ListCommand()); Add(new FindCommand()); Add(new InstallCommand()); Add(new AppleResetSimulatorCommand(AppleCommandSet.GetAppleDependencies())); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/GetStateCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.CLI.CommandArguments; using Microsoft.DotNet.XHarness.Common; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.DotNet.XHarness.CLI.Commands; internal abstract class GetStateCommand : XHarnessCommand where TArguments : IXHarnessCommandArguments { private const string CommandHelp = "Print information about the current machine, such as host machine info and device status"; protected override string CommandDescription { get; } = CommandHelp; public GetStateCommand(TargetPlatform targetPlatform, ServiceCollection services) : base(targetPlatform, "state", allowsExtraArgs: false, services, CommandHelp) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/WASI/Engine/WasiTestCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasi; using Microsoft.DotNet.XHarness.CLI.Commands.Wasm; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.Common.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasi; internal class WasiTestCommand : XHarnessCommand { private const string CommandHelp = "Executes tests on WASI using a selected engine"; protected override WasiTestCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "wasi test [OPTIONS] -- [ENGINE OPTIONS]"; protected override string CommandDescription { get; } = CommandHelp; public WasiTestCommand() : base(TargetPlatform.WASI, "test", true, new ServiceCollection(), CommandHelp) { } protected override async Task InvokeInternal(ILogger logger) { var processManager = ProcessManagerFactory.CreateProcessManager(); string engineBinary = Arguments.Engine.Value switch { WasmEngine.WasmTime => "wasmtime", _ => throw new ArgumentException("Engine not set") }; if (!string.IsNullOrEmpty(Arguments.EnginePath.Value)) { engineBinary = Arguments.EnginePath.Value; if (Path.IsPathRooted(engineBinary) && !File.Exists(engineBinary)) throw new ArgumentException($"Could not find wasi engine at the specified path - {engineBinary}"); } else { if (!FileUtils.TryFindExecutableInPATH(engineBinary, out string? foundBinary, out string? errorMessage)) { logger.LogCritical($"The engine binary `{engineBinary}` was not found. {errorMessage}"); return ExitCode.APP_LAUNCH_FAILURE; } engineBinary = foundBinary; } var cts = new CancellationTokenSource(); try { logger.LogInformation($"Using wasm engine {Arguments.Engine.Value} from path {engineBinary}"); await PrintVersionAsync(Arguments.Engine.Value.Value, engineBinary); ServerURLs? serverURLs = null; if (Arguments.IsWebServerEnabled) { serverURLs = await WebServer.Start( Arguments, logger, cts.Token); } var engineArgs = new List(); engineArgs.AddRange(Arguments.EngineArgs.Value); if (Arguments.IsWebServerEnabled) { foreach (var envVariable in Arguments.WebServerHttpEnvironmentVariables.Value) { engineArgs.Add($"--env"); engineArgs.Add($"{envVariable}={serverURLs!.Http}"); } if (Arguments.WebServerUseHttps) { foreach (var envVariable in Arguments.WebServerHttpsEnvironmentVariables.Value) { engineArgs.Add($"--env"); engineArgs.Add($"{envVariable}={serverURLs!.Https}"); } } } engineArgs.AddRange(PassThroughArguments); var xmlResultsFilePath = Path.Combine(Arguments.OutputDirectory, "testResults.xml"); File.Delete(xmlResultsFilePath); var stdoutFilePath = Path.Combine(Arguments.OutputDirectory, "wasi-console.log"); File.Delete(stdoutFilePath); var logProcessor = new WasmTestMessagesProcessor(xmlResultsFilePath, stdoutFilePath, logger); var logProcessorTask = Task.Run(() => logProcessor.RunAsync(cts.Token)); var processTask = processManager.ExecuteCommandAsync( engineBinary, engineArgs, log: new CallbackLog(m => logger.LogInformation(m.Trim())), stdoutLog: new CallbackLog(msg => logProcessor.Invoke(msg)), stderrLog: new CallbackLog(logProcessor.ProcessErrorMessage), Arguments.Timeout); var tasks = new Task[] { logProcessorTask, processTask, Task.Delay(Arguments.Timeout) }; var task = await Task.WhenAny(tasks).ConfigureAwait(false); if (task == tasks[^1] || cts.IsCancellationRequested || task.IsCanceled) { logger.LogError($"Tests timed out after {((TimeSpan)Arguments.Timeout).TotalSeconds}secs"); if (!cts.IsCancellationRequested) cts.Cancel(); return ExitCode.TIMED_OUT; } if (task.IsFaulted) { logger.LogError($"task faulted {task.Exception}"); throw task.Exception!; } // if the log processor completed without errors, then the // process should be done too, or about to be done! var result = await processTask; ExitCode logProcessorExitCode = await logProcessor.CompleteAndFlushAsync(); if (logProcessor.ForwardedExitCode != null) { // until WASI can work with unix exit code https://github.com/WebAssembly/wasi-cli/pull/44 result.ExitCode = logProcessor.ForwardedExitCode.Value; } if (result.ExitCode != Arguments.ExpectedExitCode) { logger.LogError($"Application has finished with exit code {result.ExitCode} but {Arguments.ExpectedExitCode} was expected"); return ExitCode.GENERAL_FAILURE; } else { logger.LogInformation("Application has finished with exit code: " + result.ExitCode); // return SUCCESS if logProcess also returned SUCCESS return logProcessorExitCode; } } catch (Win32Exception e) when (e.NativeErrorCode == 2) { logger.LogCritical($"The engine binary `{engineBinary}` was not found"); return ExitCode.APP_LAUNCH_FAILURE; } finally { if (!cts.IsCancellationRequested) { cts.Cancel(); } } Task PrintVersionAsync(WasmEngine engine, string engineBinary) { return processManager.ExecuteCommandAsync( engineBinary, new[] { "--version" }, log: new CallbackLog(m => logger.LogDebug(m.Trim())), stdoutLog: new CallbackLog(msg => logger.LogInformation(msg.Trim())), stderrLog: new CallbackLog(msg => logger.LogError(msg.Trim())), TimeSpan.FromSeconds(10)); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/WASI/WasiCommandSet.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Mono.Options; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasi; // Main WASI command set that contains the platform specific commands. This allows the command line to // support different options in different platforms. // Whenever the behavior does match, the goal is to have the same arguments for all platforms public class WasiCommandSet : CommandSet { public WasiCommandSet() : base("wasi") { Add(new WasiTestCommand()); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net.WebSockets; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Web; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; using OpenQA.Selenium; using OpenQA.Selenium.DevTools; using OpenQA.Selenium.Support.UI; using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm; internal class WasmBrowserTestRunner { private readonly WasmTestBrowserCommandArguments _arguments; private readonly ILogger _logger; private readonly IEnumerable _passThroughArguments; private readonly WasmTestMessagesProcessor _messagesProcessor; // Messages from selenium prepend the url, and location where the message originated // Eg. `foo` becomes `http://localhost:8000/xyz.js 0:12 "foo" static readonly Regex s_consoleLogRegex = new(@"^\s*[a-z]*://[^\s]+\s+\d+:\d+\s+""(.*)""\s*$", RegexOptions.Compiled); public WasmBrowserTestRunner(WasmTestBrowserCommandArguments arguments, IEnumerable passThroughArguments, WasmTestMessagesProcessor messagesProcessor, ILogger logger) { _arguments = arguments; _logger = logger; _passThroughArguments = passThroughArguments; _messagesProcessor = messagesProcessor; } public async Task RunTestsWithWebDriver(DriverService driverService, IWebDriver driver) { var htmlFilePath = Path.Combine(_arguments.AppPackagePath, _arguments.HTMLFile.Value); if (!File.Exists(htmlFilePath)) { _logger.LogError($"Could not find html file {htmlFilePath}"); return ExitCode.GENERAL_FAILURE; } var cts = new CancellationTokenSource(); try { var consolePumpTcs = new TaskCompletionSource(); var logProcessorTask = Task.Run(() => _messagesProcessor.RunAsync(cts.Token)); var webServerOptions = WebServer.TestWebServerOptions.FromArguments(_arguments); webServerOptions.ContentRoot = _arguments.AppPackagePath; webServerOptions.OnConsoleConnected = socket => RunConsoleMessagesPump(socket, cts.Token); ServerURLs serverURLs = await WebServer.Start( webServerOptions, _logger, cts.Token); string testUrl = BuildUrl(serverURLs); var devTools = driver as IDevTools; // firefox does not support devtools protocol, we use websocket to push console logs from Firefox if (devTools != null) { var session = devTools.CreateDevToolsSession(); await session.Console.Enable(); session.Console.MessageAdded += Console_MessageAdded; void Console_MessageAdded(object? sender, OpenQA.Selenium.DevTools.Console.MessageAddedEventArgs e) { var text = e.Message.Text; var match = s_consoleLogRegex.Match(Regex.Unescape(text)); string msg = match.Success ? match.Groups[1].Value : text; _messagesProcessor.Invoke(msg); } } cts.CancelAfter(_arguments.Timeout); _logger.LogDebug($"Opening in browser: {testUrl}"); driver.Navigate().GoToUrl(testUrl); TaskCompletionSource wasmExitReceivedTcs = _messagesProcessor.WasmExitReceivedTcs; var tasks = new Task[] { wasmExitReceivedTcs.Task, consolePumpTcs.Task, logProcessorTask, Task.Delay(_arguments.Timeout) }; if (_arguments.BackgroundThrottling) { // throttling only happens when the page is not visible driver.Manage().Window.Minimize(); } var task = await Task.WhenAny(tasks).ConfigureAwait(false); ExitCode logProcessorExitCode = ExitCode.SUCCESS; if (task != logProcessorTask && !task.IsFaulted) logProcessorExitCode = await _messagesProcessor.CompleteAndFlushAsync(); if (task == tasks[^1] || cts.IsCancellationRequested) { if (driverService.IsRunning) { // Selenium isn't able to kill chrome in this case :/ int pid = driverService.ProcessId; var p = Process.GetProcessById(pid); if (p != null) { _logger.LogError($"Tests timed out. Killing driver service pid {pid}"); p.Kill(true); } } // timed out if (!cts.IsCancellationRequested) cts.Cancel(); return ExitCode.TIMED_OUT; } if (task == wasmExitReceivedTcs.Task && wasmExitReceivedTcs.Task.IsCompletedSuccessfully) { _logger.LogTrace($"Looking for `tests_done` element, to get the exit code"); var testsDoneElement = new WebDriverWait(driver, TimeSpan.FromSeconds(30)) .Until(e => e.FindElement(By.Id("tests_done"))); if (int.TryParse(testsDoneElement.Text, out var code)) { var appExitCode = (ExitCode)Enum.ToObject(typeof(ExitCode), code); if (logProcessorExitCode != ExitCode.SUCCESS) { _logger.LogInformation($"Application has finished with exit code {appExitCode}. But the log processor failed with {logProcessorExitCode}."); return logProcessorExitCode; } return appExitCode; } return ExitCode.RETURN_CODE_NOT_SET; } if (task.IsFaulted) { _logger.LogDebug($"task faulted {task.Exception}"); throw task.Exception!; } return ExitCode.TIMED_OUT; } finally { if (!cts.IsCancellationRequested) { cts.Cancel(); } } } private async Task RunConsoleMessagesPump(WebSocket socket, CancellationToken token) { byte[] buff = new byte[4000]; var mem = new MemoryStream(); try { while (!token.IsCancellationRequested) { if (socket.State != WebSocketState.Open) { _logger.LogError($"DevToolsProxy: Socket is no longer open."); return; } WebSocketReceiveResult result = await socket.ReceiveAsync(new ArraySegment(buff), token).ConfigureAwait(false); if (result.MessageType == WebSocketMessageType.Close) return; mem.Write(buff, 0, result.Count); if (result.EndOfMessage) { var line = Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int)mem.Length); line += Environment.NewLine; await _messagesProcessor.InvokeAsync(line, token); mem.SetLength(0); mem.Seek(0, SeekOrigin.Begin); } } } catch (WebSocketException wse) { // this could happen when WebWorker is closed or when browser died _logger.LogDebug($"RunConsoleMessagesPump failed: {wse}"); } catch (OperationCanceledException oce) { if (!token.IsCancellationRequested) _logger.LogDebug($"RunConsoleMessagesPump cancelled: {oce}"); } finally { _logger.LogDebug($"Reading console messages from websocket stopped"); } } private string BuildUrl(ServerURLs serverURLs) { var uriBuilder = new UriBuilder($"{serverURLs.Http}/{_arguments.HTMLFile}"); var sb = new StringBuilder(); if (_arguments.DebuggerPort.Value != null) sb.Append($"arg=--debug"); foreach (var envVariable in _arguments.WebServerHttpEnvironmentVariables.Value) { if (sb.Length > 0) sb.Append('&'); sb.Append($"arg={HttpUtility.UrlEncode($"--setenv={envVariable}={serverURLs!.Http}")}"); } if (_arguments.WebServerUseHttps) { foreach (var envVariable in _arguments.WebServerHttpsEnvironmentVariables.Value) { if (sb.Length > 0) sb.Append('&'); sb.Append($"arg={HttpUtility.UrlEncode($"--setenv={envVariable}={serverURLs!.Https}")}"); } } foreach (var arg in _passThroughArguments) { if (sb.Length > 0) sb.Append('&'); sb.Append($"arg={HttpUtility.UrlEncode(arg)}"); } if (sb.Length > 0) sb.Append('&'); sb.Append($"arg=-verbosity&arg={VerbosityToString()}"); uriBuilder.Query = sb.ToString(); return uriBuilder.ToString(); } // MinimumLogLevel.Critical, // MinimumLogLevel.Error, // MinimumLogLevel.Warning, // MinimumLogLevel.Info, // MinimumLogLevel.Debug, // MinimumLogLevel.Verbose private string VerbosityToString() => _arguments.Verbosity.Value switch { LogLevel.Trace => "Verbose", LogLevel.Debug => "Debug", LogLevel.Information => "Info", LogLevel.Warning => "Warning", LogLevel.Error => "Error", LogLevel.Critical => "Critical", LogLevel.None => "Critical", _ => throw new NotSupportedException($"The value '{_arguments.Verbosity.Value}' is not supported in conversion to MinimumLogLevel") }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmTestBrowserCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Chromium; using OpenQA.Selenium.Edge; using OpenQA.Selenium.Firefox; using OpenQA.Selenium.Safari; using SeleniumLogLevel = OpenQA.Selenium.LogLevel; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm; internal class WasmTestBrowserCommand : XHarnessCommand { private const string CommandHelp = "Executes tests on WASM using a browser"; protected override string CommandUsage { get; } = "wasm test-browser [OPTIONS] -- [BROWSER OPTIONS]"; protected override string CommandDescription { get; } = CommandHelp; protected override WasmTestBrowserCommandArguments Arguments { get; } = new(); public WasmTestBrowserCommand() : base(TargetPlatform.WASM, "test-browser", allowsExtraArgs: true, new ServiceCollection(), CommandHelp) { } protected override async Task InvokeInternal(ILogger logger) { var xmlResultsFilePath = Path.Combine(Arguments.OutputDirectory, "testResults.xml"); File.Delete(xmlResultsFilePath); var stdoutFilePath = Path.Combine(Arguments.OutputDirectory, "wasm-console.log"); File.Delete(stdoutFilePath); var symbolicator = WasmSymbolicatorBase.Create(Arguments.SymbolicatorArgument.GetLoadedTypes().FirstOrDefault(), Arguments.SymbolMapFileArgument, Arguments.SymbolicatePatternsFileArgument, logger); var serviceProvider = Services.BuildServiceProvider(); var diagnosticsData = serviceProvider.GetRequiredService(); var coverageOutputPath = Arguments.EnableCoverage ? Path.Combine(Arguments.OutputDirectory, "coverage.cobertura.xml") : null; var logProcessor = new WasmTestMessagesProcessor(xmlResultsFilePath, stdoutFilePath, logger, Arguments.ErrorPatternsFile, symbolicator, coverageOutputPath); var runner = new WasmBrowserTestRunner( Arguments, PassThroughArguments, logProcessor, logger); diagnosticsData.Target = Arguments.Browser.Value.ToString(); (DriverService driverService, IWebDriver driver) = Arguments.Browser.Value switch { Browser.Chrome => GetChromeDriver(Arguments.Locale, logger), Browser.Safari => GetSafariDriver(logger), Browser.Firefox => GetFirefoxDriver(logger), Browser.Edge => GetEdgeDriver(Arguments.Locale, logger), // shouldn't reach here _ => throw new ArgumentException($"Unknown browser : {Arguments.Browser}") }; try { var exitCode = await runner.RunTestsWithWebDriver(driverService, driver); if ((int)exitCode != Arguments.ExpectedExitCode) { logger.LogError($"Application has finished with exit code {exitCode} but {Arguments.ExpectedExitCode} was expected"); return ExitCode.GENERAL_FAILURE; } if (logProcessor.LineThatMatchedErrorPattern != null) { logger.LogError("Application exited with the expected exit code: {exitCode}." + $" But found a line matching an error pattern: {logProcessor.LineThatMatchedErrorPattern}"); return ExitCode.APP_CRASH; } return ExitCode.SUCCESS; } finally { if (Arguments.NoQuit) { logger.LogInformation("Tests are done. Press Ctrl+C to exit"); var token = new CancellationToken(false); token.WaitHandle.WaitOne(); } // close all tabs before quit is a workaround for broken Selenium - GeckoDriver communication in Firefox // https://github.com/dotnet/runtime/issues/101617 var cts = new CancellationTokenSource(); cts.CancelAfter(10000); try { logger.LogInformation($"Closing {driver.WindowHandles.Count} browser tabs before setting the main tab to config page and quitting."); while (driver.WindowHandles.Count > 1 && driverService.IsRunning) { if (cts.IsCancellationRequested) { logger.LogInformation($"Timeout while trying to close tabs, {driver.WindowHandles.Count} is left open before quitting."); break; } driver.Navigate().GoToUrl("about:config"); driver.Navigate().GoToUrl("about:blank"); driver.Close(); //Close Tab var lastWindowHandle = driver.WindowHandles.LastOrDefault(); if (lastWindowHandle != null) { driver.SwitchTo().Window(lastWindowHandle); } } await Task.Delay(TimeSpan.FromSeconds(1), cts.Token); if (driverService.IsRunning) { if (!cts.IsCancellationRequested && driver.WindowHandles.Count != 0) { driver.Navigate().GoToUrl("about:config"); driver.Navigate().GoToUrl("about:blank"); } driver.Quit(); // Firefox driver hangs if Quit is not issued. driver.Dispose(); driverService.Dispose(); } } catch (Exception e) { logger.LogError($"Error while closing browser: {e}"); } } } private (DriverService, IWebDriver) GetSafariDriver(ILogger logger) { var options = new SafariOptions(); options.SetLoggingPreference(LogType.Browser, SeleniumLogLevel.All); logger.LogInformation("Starting Safari"); return CreateWebDriver( () => SafariDriverService.CreateDefaultService(), driverService => new SafariDriver(driverService, options, Arguments.Timeout)); } private (DriverService, IWebDriver) GetFirefoxDriver(ILogger logger) { var options = new FirefoxOptions(); options.SetLoggingPreference(LogType.Browser, SeleniumLogLevel.All); if (!string.IsNullOrEmpty(Arguments.BrowserLocation)) { options.BrowserExecutableLocation = Arguments.BrowserLocation; logger.LogInformation($"Using Firefox from {Arguments.BrowserLocation}"); } options.AddArguments(Arguments.BrowserArgs.Value); if (!Arguments.NoHeadless) options.AddArguments("--headless"); if (!Arguments.NoIncognito) options.AddArguments("-private-window"); options.PageLoadStrategy = Arguments.PageLoadStrategy.Value; logger.LogInformation($"Starting Firefox with args: {string.Join(' ', options.ToCapabilities())} and load strategy: {Arguments.PageLoadStrategy.Value}"); string[] err_snippets = new[] { "exited abnormally", "Cannot start the driver service", "failed to start", "Connection refused" }; int max_retries = 3; int retry_num = 0; while (true) { FirefoxDriverService? driverService = null; try { driverService = FirefoxDriverService.CreateDefaultService(); return (driverService, new FirefoxDriver(driverService, options, Arguments.Timeout)); } catch (WebDriverException wde) when (err_snippets.Any(s => wde.ToString().Contains(s)) && retry_num < max_retries - 1) { // geckodriver can fail with "Cannot start the driver service" due to // a race between the driver process binding to a port and Selenium // trying to connect. Retry a few times as a workaround. logger.LogWarning($"Failed to start Firefox, attempt #{retry_num}: {wde}"); driverService?.Dispose(); } catch { driverService?.Dispose(); throw; } retry_num++; } } private (DriverService, IWebDriver) GetChromeDriver(string sessionLanguage, ILogger logger) => GetChromiumDriver( "chromedriver", sessionLanguage, options => ChromeDriverService.CreateDefaultService(), logger); private (DriverService, IWebDriver) GetEdgeDriver(string sessionLanguage, ILogger logger) => GetChromiumDriver( "edgedriver", sessionLanguage, options => { options.UseChromium = true; return EdgeDriverService.CreateDefaultServiceFromOptions(options); }, logger); private (DriverService, IWebDriver) GetChromiumDriver( string driverName, string sessionLanguage, Func getDriverService, ILogger logger) where TDriver : ChromiumDriver where TDriverOptions : ChromiumOptions where TDriverService : ChromiumDriverService { var options = Activator.CreateInstance(); options.SetLoggingPreference(LogType.Browser, SeleniumLogLevel.All); if (!string.IsNullOrEmpty(Arguments.BrowserLocation)) { options.BinaryLocation = Arguments.BrowserLocation; logger.LogInformation($"Using Chrome from {Arguments.BrowserLocation}"); } options.AddArguments(Arguments.BrowserArgs.Value); if (!Arguments.NoHeadless && !Arguments.BackgroundThrottling) options.AddArguments("--headless"); if (Arguments.DebuggerPort.Value != null) options.AddArguments($"--remote-debugging-port={Arguments.DebuggerPort}"); if (!Arguments.NoIncognito) options.AddArguments("--incognito"); if (!Arguments.BackgroundThrottling) { options.AddArguments(new[] { "--disable-background-timer-throttling", "--disable-backgrounding-occluded-windows", "--disable-renderer-backgrounding", "--enable-features=NetworkService,NetworkServiceInProcess", }); } else { options.AddArguments(@"--enable-features=IntensiveWakeUpThrottling:grace_period_seconds/1"); } options.AddArguments(new[] { // added based on https://github.com/puppeteer/puppeteer/blob/main/src/node/Launcher.ts#L159-L181 "--allow-insecure-localhost", "--disable-breakpad", "--disable-component-extensions-with-background-pages", "--disable-dev-shm-usage", "--disable-extensions", "--disable-features=TranslateUI", "--disable-ipc-flooding-protection", "--force-color-profile=srgb", "--metrics-recording-only" }); if (File.Exists("/.dockerenv") || Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true") { // Use --no-sandbox for containers (Linux Docker, Windows containers, and codespaces) options.AddArguments("--no-sandbox"); } if (Arguments.NoQuit) options.LeaveBrowserRunning = true; if (options is ChromeOptions chromeOptions) chromeOptions.PageLoadStrategy = Arguments.PageLoadStrategy.Value; if (options is EdgeOptions edgeOptions) edgeOptions.PageLoadStrategy = Arguments.PageLoadStrategy.Value; logger.LogInformation($"Starting {driverName} with args: {string.Join(' ', options.Arguments)} and load strategy: {Arguments.PageLoadStrategy.Value}"); // We want to explicitly specify a timeout here. This is for for the // driver commands, like getLog. The default is 60s, which ends up // timing out when getLog() is waiting, and doesn't receive anything // for 60s. // // Since, we almost all the output gets written via the websocket now, // getLog() might not see anything for long durations! // // So -> use a larger timeout! string[] err_snippets = new[] { "exited abnormally", "Cannot start the driver service", "failed to start", "DevToolsActivePort file doesn't exist", "session not created" }; foreach (var file in Directory.EnumerateFiles(Arguments.OutputDirectory, $"{driverName}-*.log")) File.Delete(file); int max_retries = 3; int retry_num = 0; while (true) { TDriverService? driverService = null; try { driverService = getDriverService(options); driverService.DriverProcessStarting += (object? sender, DriverProcessStartingEventArgs e) => { // Browser respects LANGUAGE in the first place, only if empty it checks LANG e.DriverServiceProcessStartInfo.EnvironmentVariables["LANGUAGE"] = sessionLanguage; }; driverService.EnableAppendLog = false; driverService.EnableVerboseLogging = true; driverService.LogPath = Path.Combine(Arguments.OutputDirectory, $"{driverName}-{retry_num}.log"); if (Activator.CreateInstance(typeof(TDriver), driverService, options, Arguments.Timeout.Value) is not TDriver driver) { throw new ArgumentException($"Failed to create instance of {typeof(TDriver)}"); } return (driverService, driver); } catch (TargetInvocationException tie) when (tie.InnerException is Exception inner && (inner is WebDriverException || inner is InvalidOperationException) && err_snippets.Any(s => inner.ToString().Contains(s)) && retry_num < max_retries - 1) { // chrome can sometimes crash on startup when launching from chromedriver. // As a *workaround*, let's retry that a few times // Example errors seen: // OpenQA.Selenium.WebDriverException: unknown error: Chrome failed to start: exited abnormally. // (chrome not reachable) // System.InvalidOperationException: session not created: Chrome failed to start: crashed. // (session not created: DevToolsActivePort file doesn't exist) // System.InvalidOperationException: session not created: Chrome instance exited. // (SessionNotCreated) // Log on max-1 tries, and rethrow on the last one logger.LogWarning($"Failed to start the browser, attempt #{retry_num}: {inner}"); driverService?.Dispose(); } catch { driverService?.Dispose(); throw; } retry_num++; } } private static (DriverService, IWebDriver) CreateWebDriver(Func getDriverService, Func getDriver) where TDriverService : DriverService { var driverService = getDriverService(); try { return (driverService, getDriver(driverService)); } catch { driverService?.Dispose(); throw; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/ErrorPatternScanner.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm; public class ErrorPatternScanner { private readonly ILogger _logger; private readonly List _errorPatternStrings = new(); private readonly List _errorPatternRegexes = new(); private readonly bool _empty; public ErrorPatternScanner(string patternsFile, ILogger logger) { _logger = logger; if (string.IsNullOrEmpty(patternsFile)) throw new ArgumentNullException(nameof(patternsFile)); if (!File.Exists(patternsFile)) throw new FileNotFoundException(patternsFile); foreach (string line in File.ReadAllLines(patternsFile)) { if (line.Trim().Length <= 1) continue; char type = line[0]; string pattern = line[1..]; switch (type) { case '#': // comment break; case '@': _errorPatternStrings.Add(pattern); break; case '%': { try { _errorPatternRegexes.Add(new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase)); } catch (Exception ex) when (ex is ArgumentException || ex is ArgumentNullException || ex is ArgumentOutOfRangeException) { _logger.LogWarning($"ErrorPatternScanner: Failed to compile regex error pattern '{pattern}': {ex.Message}"); } } break; default: _logger.LogWarning($"ErrorPatternScanner: Unknown type prefix '{type}' on line '{line}'. Ignoring."); break; } } _empty = _errorPatternRegexes.Count == 0 && _errorPatternStrings.Count == 0; } public bool IsError(string line, out string? matchedPattern) { matchedPattern = null; if (_empty) return false; string? patternString = _errorPatternStrings.FirstOrDefault(pattern => line.Contains(pattern, StringComparison.InvariantCultureIgnoreCase)); if (patternString != null) { matchedPattern = patternString; return true; } Regex? matchedRegex = _errorPatternRegexes.FirstOrDefault(regex => regex.IsMatch(line)); if (matchedRegex != null) { matchedPattern = matchedRegex.ToString(); return true; } return false; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.Common.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm; internal class WasmTestCommand : XHarnessCommand { private const string CommandHelp = "Executes tests on WASM using a selected JavaScript engine"; protected override WasmTestCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "wasm test [OPTIONS] -- [ENGINE OPTIONS]"; protected override string CommandDescription { get; } = CommandHelp; public WasmTestCommand() : base(TargetPlatform.WASM, "test", true, new ServiceCollection(), CommandHelp) { } protected override async Task InvokeInternal(ILogger logger) { var processManager = ProcessManagerFactory.CreateProcessManager(); string engineBinary = Arguments.Engine.Value switch { JavaScriptEngine.V8 => "v8", JavaScriptEngine.JavaScriptCore => "jsc", JavaScriptEngine.SpiderMonkey => "sm", JavaScriptEngine.NodeJS => "node", _ => throw new ArgumentException("Engine not set") }; if (!string.IsNullOrEmpty(Arguments.EnginePath.Value)) { engineBinary = Arguments.EnginePath.Value; if (Path.IsPathRooted(engineBinary) && !File.Exists(engineBinary)) throw new ArgumentException($"Could not find js engine at the specified path - {engineBinary}"); } else { if (!FileUtils.TryFindExecutableInPATH(engineBinary, out string? foundBinary, out string? errorMessage)) { logger.LogCritical($"The engine binary `{engineBinary}` was not found. {errorMessage}"); return ExitCode.APP_LAUNCH_FAILURE; } engineBinary = foundBinary; } var serviceProvider = Services.BuildServiceProvider(); var diagnosticsData = serviceProvider.GetRequiredService(); diagnosticsData.Target = engineBinary; var cts = new CancellationTokenSource(); try { logger.LogInformation($"Using js engine {Arguments.Engine.Value} from path {engineBinary}"); string? versionString = await GetVersionAsync(Arguments.Engine.Value.Value, engineBinary); diagnosticsData.TargetOS = versionString; ServerURLs? serverURLs = null; if (Arguments.IsWebServerEnabled) { serverURLs = await WebServer.Start( Arguments, logger, cts.Token); } var engineArgs = new List(); if (Arguments.Engine == JavaScriptEngine.V8) { // v8 needs this flag to enable WASM support engineArgs.Add("--expose_wasm"); } engineArgs.AddRange(Arguments.EngineArgs.Value); engineArgs.Add(Arguments.JSFile); if (Arguments.Engine == JavaScriptEngine.V8 || Arguments.Engine == JavaScriptEngine.JavaScriptCore) { // v8/jsc want arguments to the script separated by "--", others don't engineArgs.Add("--"); } if (Arguments.IsWebServerEnabled) { foreach (var envVariable in Arguments.WebServerHttpEnvironmentVariables.Value) { engineArgs.Add($"--setenv={envVariable}={serverURLs!.Http}"); } if (Arguments.WebServerUseHttps) { foreach (var envVariable in Arguments.WebServerHttpsEnvironmentVariables.Value) { engineArgs.Add($"--setenv={envVariable}={serverURLs!.Https}"); } } } engineArgs.AddRange(PassThroughArguments); var xmlResultsFilePath = Path.Combine(Arguments.OutputDirectory, "testResults.xml"); File.Delete(xmlResultsFilePath); var stdoutFilePath = Path.Combine(Arguments.OutputDirectory, "wasm-console.log"); File.Delete(stdoutFilePath); var symbolicator = WasmSymbolicatorBase.Create(Arguments.SymbolicatorArgument.GetLoadedTypes().FirstOrDefault(), Arguments.SymbolMapFileArgument, Arguments.SymbolicatePatternsFileArgument, logger); var coverageOutputPath = Arguments.EnableCoverage ? Path.Combine(Arguments.OutputDirectory, "coverage.cobertura.xml") : null; var logProcessor = new WasmTestMessagesProcessor(xmlResultsFilePath, stdoutFilePath, logger, Arguments.ErrorPatternsFile, symbolicator, coverageOutputPath); var logProcessorTask = Task.Run(() => logProcessor.RunAsync(cts.Token)); var processTask = processManager.ExecuteCommandAsync( engineBinary, engineArgs, log: new CallbackLog(m => logger.LogInformation(m)), stdoutLog: new CallbackLog(msg => logProcessor.Invoke(msg)), stderrLog: new CallbackLog(logProcessor.ProcessErrorMessage), Arguments.Timeout, // Node respects LANG only, ignores LANGUAGE environmentVariables: Arguments.Engine.Value == JavaScriptEngine.NodeJS ? new Dictionary() { { "LANG", Arguments.Locale } } : null); TaskCompletionSource wasmExitReceivedTcs = logProcessor.WasmExitReceivedTcs; var tasks = new Task[] { wasmExitReceivedTcs.Task, logProcessorTask, processTask, Task.Delay(Arguments.Timeout) }; var task = await Task.WhenAny(tasks).ConfigureAwait(false); if (task == tasks[^1] || cts.IsCancellationRequested || task.IsCanceled) { logger.LogError($"Tests timed out after {((TimeSpan)Arguments.Timeout).TotalSeconds}secs"); if (!cts.IsCancellationRequested) cts.Cancel(); return ExitCode.TIMED_OUT; } if (task.IsFaulted) { logger.LogError($"task faulted {task.Exception}"); throw task.Exception!; } // if the log processor completed without errors, then the // process should be done too, or about to be done! var result = await processTask; ExitCode logProcessorExitCode = await logProcessor.CompleteAndFlushAsync(); // give messages bit more time if (!wasmExitReceivedTcs.Task.IsCompleted) { await Task.WhenAny(new Task[] { wasmExitReceivedTcs.Task, Task.Delay(200) }).ConfigureAwait(false); } if (result.ExitCode != Arguments.ExpectedExitCode) { logger.LogError($"Application has finished with exit code {result.ExitCode} but {Arguments.ExpectedExitCode} was expected"); return ExitCode.GENERAL_FAILURE; } else { if(!wasmExitReceivedTcs.Task.IsCompletedSuccessfully) { logger.LogError("Application exited without sending a wasm exit message."); // TODO return RETURN_CODE_NOT_SET when we are more confident that it doesn't happen very often // return ExitCode.RETURN_CODE_NOT_SET; } if (logProcessor.LineThatMatchedErrorPattern != null) { logger.LogError("Application exited with the expected exit code: {result.ExitCode}." + $" But found a line matching an error pattern: {logProcessor.LineThatMatchedErrorPattern}"); return ExitCode.APP_CRASH; } logger.LogInformation("Application has finished with exit code: " + result.ExitCode); // return SUCCESS if logProcess also returned SUCCESS return logProcessorExitCode; } } catch (Win32Exception e) when (e.NativeErrorCode == 2) { logger.LogCritical($"The engine binary `{engineBinary}` was not found"); return ExitCode.APP_LAUNCH_FAILURE; } finally { if (!cts.IsCancellationRequested) { cts.Cancel(); } } async Task GetVersionAsync(JavaScriptEngine engine, string engineBinary) { string[] args; switch (engine) { case JavaScriptEngine.V8: args = new[] { "-e", "console.log(this.version())" }; break; case JavaScriptEngine.NodeJS: args = new[] { "--version" }; break; default: return null; }; StringBuilder output = new(); await processManager.ExecuteCommandAsync( engineBinary, args, log: new CallbackLog(m => logger.LogDebug(m.Trim())), stdoutLog: new CallbackLog(msg => { logger.LogInformation(msg.Trim()); output.Append(msg.Trim()); }), stderrLog: new CallbackLog(msg => logger.LogError(msg.Trim())), TimeSpan.FromSeconds(10)); return output.ToString(); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WasmCommandSet.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Mono.Options; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm; // Main WASM command set that contains the platform specific commands. This allows the command line to // support different options in different platforms. // Whenever the behavior does match, the goal is to have the same arguments for all platforms public class WasmCommandSet : CommandSet { public WasmCommandSet() : base("wasm") { Add(new WasmTestCommand()); Add(new WasmTestBrowserCommand()); Add(new WebServerCommand()); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WasmLogMessage.cs ================================================ namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm; class WasmLogMessage { public string? method { get; set; } public string? payload { get; set; } public object[]? arguments { get; set; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WasmTestMessagesProcessor.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Linq; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm; public class WasmTestMessagesProcessor { private static Regex xmlRx = new Regex(@"^STARTRESULTXML ([0-9]*) ([^ ]*) ENDRESULTXML", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static Regex coverageRx = new Regex(@"^STARTCOVERAGEXML ([0-9]*) ([^ ]*) ENDCOVERAGEXML", RegexOptions.Compiled | RegexOptions.CultureInvariant); private readonly StreamWriter _stdoutFileWriter; private readonly string _xmlResultsFilePath; private readonly string? _coverageOutputPath; private static TimeSpan s_logMessagesTimeout = TimeSpan.FromMinutes(2); private readonly ILogger _logger; private readonly Lazy? _errorScanner; private readonly WasmSymbolicatorBase? _symbolicator; private readonly ChannelReader<(string, bool)> _channelReader; private readonly ChannelWriter<(string, bool)> _channelWriter; private readonly TaskCompletionSource _completed = new (); private bool _isRunning => !_completed.Task.IsCompleted; private bool _loggedProcessorStopped = false; public string? LineThatMatchedErrorPattern { get; private set; } // Set once `WASM EXIT` message is received public TaskCompletionSource WasmExitReceivedTcs { get; } = new (); public int? ForwardedExitCode {get; private set; } public WasmTestMessagesProcessor(string xmlResultsFilePath, string stdoutFilePath, ILogger logger, string? errorPatternsFile = null, WasmSymbolicatorBase? symbolicator = null, string? coverageOutputPath = null) { _xmlResultsFilePath = xmlResultsFilePath; _coverageOutputPath = coverageOutputPath; _stdoutFileWriter = File.CreateText(stdoutFilePath); _stdoutFileWriter.AutoFlush = true; _logger = logger; if (errorPatternsFile != null) { if (!File.Exists(errorPatternsFile)) throw new ArgumentException($"Cannot find error patterns file {errorPatternsFile}"); _errorScanner = new Lazy(() => new ErrorPatternScanner(errorPatternsFile, logger)); } _symbolicator = symbolicator; var channel = Channel.CreateUnbounded<(string, bool)>(new UnboundedChannelOptions { SingleReader = true }); _channelWriter = channel.Writer; _channelReader = channel.Reader; } public async Task RunAsync(CancellationToken token) { try { await foreach ((string line, bool isError) in _channelReader.ReadAllAsync(token)) { ProcessMessage(line, isError); } _completed.SetResult(); } catch (Exception ex) { _channelWriter.TryComplete(ex); // surface the exception from task for this method // and from _completed _completed.SetException(ex); throw; } } public void Invoke(string message, bool isError = false) { WarnOnceIfStopped(); if (_isRunning && _channelWriter.TryWrite((message, isError))) return; LogMessage(message.TrimEnd(), isError); } public Task InvokeAsync(string message, CancellationToken token, bool isError = false) { string? logMsg; try { WarnOnceIfStopped(); if (_isRunning) return _channelWriter.WriteAsync((message, isError), token).AsTask(); logMsg = message.TrimEnd(); } catch (ChannelClosedException cce) { logMsg = $"Failed to write to the channel - {cce.Message}. Message: {message}"; } LogMessage(logMsg, isError); return Task.CompletedTask; } private void WarnOnceIfStopped() { if (!_isRunning && !_loggedProcessorStopped) { _logger.LogWarning($"Message processor is not running anymore."); _loggedProcessorStopped = true; } } private void LogMessage(string message, bool isError) { if (isError) _logger.LogError(message); else _logger.LogInformation(message); } public async Task CompleteAndFlushAsync(TimeSpan? timeout = null) { timeout ??= s_logMessagesTimeout; _logger.LogInformation($"Waiting to flush log messages with a timeout of {timeout.Value.TotalSeconds} secs .."); try { _channelWriter.TryComplete(); await _completed.Task.WaitAsync(timeout.Value); return ExitCode.SUCCESS; } catch (TimeoutException) { _logger.LogError($"Flushing log messages timed out after {s_logMessagesTimeout.TotalSeconds}secs"); return ExitCode.TIMED_OUT; } catch (Exception ex) { _logger.LogError($"Flushing log messages failed with: {ex}. Ignoring."); return ExitCode.GENERAL_FAILURE; } } private void ProcessMessage(string message, bool isError = false) { WasmLogMessage? logMessage = null; string line; if (message.StartsWith("{")) { try { logMessage = JsonSerializer.Deserialize(message); if (logMessage != null) { // Use payload (the formatted text) if available, otherwise join arguments. // Do not concatenate both — payload already contains the formatted output, // and arguments duplicates it for simple console.log() calls. line = logMessage.payload ?? string.Join(" ", logMessage.arguments ?? Enumerable.Empty()); } else { line = message; } } catch (JsonException e) { _logger.LogError(e.Message); line = message; } } else { line = message; } line = line.TrimEnd(); var match = xmlRx.Match(line); var coverageMatch = coverageRx.Match(line); if (match.Success) { var expectedLength = Int32.Parse(match.Groups[1].Value); using (var stream = new FileStream(_xmlResultsFilePath, FileMode.Create)) { var bytes = System.Convert.FromBase64String(match.Groups[2].Value); stream.Write(bytes); if (bytes.Length == expectedLength) { _logger.LogInformation($"Received expected {bytes.Length} of {_xmlResultsFilePath}"); } else { _logger.LogInformation($"Received {bytes.Length} of {_xmlResultsFilePath} but expected {expectedLength}"); } } } else if (coverageMatch.Success && _coverageOutputPath != null) { var expectedLength = Int32.Parse(coverageMatch.Groups[1].Value); var bytes = System.Convert.FromBase64String(coverageMatch.Groups[2].Value); var outputDir = Path.GetDirectoryName(_coverageOutputPath); if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir)) { Directory.CreateDirectory(outputDir); } File.WriteAllBytes(_coverageOutputPath, bytes); if (bytes.Length == expectedLength) { _logger.LogInformation($"Received expected {bytes.Length} bytes of coverage data, saved to {_coverageOutputPath}"); } else { _logger.LogWarning($"Received {bytes.Length} bytes of coverage data but expected {expectedLength}, saved to {_coverageOutputPath}"); } } else { ScanMessageForErrorPatterns(line); line = Symbolicate(line); switch (logMessage?.method?.ToLowerInvariant()) { case "console.debug": _logger.LogDebug(line); break; case "console.error": _logger.LogError(line); break; case "console.warn": _logger.LogWarning(line); break; case "console.trace": _logger.LogTrace(line); break; case "console.log": default: _logger.LogInformation(line); break; } if (_stdoutFileWriter.BaseStream.CanWrite) _stdoutFileWriter.WriteLine(line); } // the test runner writes this as the last line, // after the tests have run, and the xml results file // has been written to the console if (line.StartsWith("WASM EXIT")) { _logger.LogDebug("Reached wasm exit"); // until WASI can work with unix exit code https://github.com/WebAssembly/wasi-cli/pull/44 if (line.Length > 10) { // the message on WASI looks like WASM EXIT 123 // here we strip the first 10 characters and parse the rest if (int.TryParse(line.Substring(10), out var exitCode)) ForwardedExitCode = exitCode; else _logger.LogWarning($"Could not parse exit code from: {line}"); } if (!WasmExitReceivedTcs.TrySetResult()) _logger.LogDebug("Got a duplicate exit message."); } } private string Symbolicate(string msg) { if (_symbolicator is null) return msg; return _symbolicator.Symbolicate(msg); } private void ScanMessageForErrorPatterns(string message) { if (LineThatMatchedErrorPattern != null || _errorScanner == null) return; if (_errorScanner.Value.IsError(message, out string? _)) LineThatMatchedErrorPattern = message; } public void ProcessErrorMessage(string message) => Invoke(message, isError: true); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WebServerCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; using Microsoft.DotNet.XHarness.CLI.Commands; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm; internal class WebServerCommand : XHarnessCommand { private const string CommandHelp = "Starts a webserver"; protected override string CommandUsage { get; } = "wasm webserver [OPTIONS]"; protected override string CommandDescription { get; } = CommandHelp; protected override WebServerCommandArguments Arguments { get; } = new(); public WebServerCommand() : base(TargetPlatform.WASM, "webserver", allowsExtraArgs: true, new ServiceCollection(), CommandHelp) { } protected override async Task InvokeInternal(ILogger logger) { var cts = new CancellationTokenSource(); var webServerOptions = WebServer.TestWebServerOptions.FromArguments(Arguments); webServerOptions.ContentRoot = Arguments.AppPackagePath; ServerURLs serverURLs = await WebServer.Start( webServerOptions, logger, cts.Token); logger.LogInformation($"Now listening on: http://{serverURLs.Http}"); if (!string.IsNullOrEmpty(serverURLs.Https)) logger.LogInformation($"Now listening on: https://{serverURLs.Https}"); await Task.Delay(Arguments.Timeout, cts.Token); if (cts.Token.IsCancellationRequested) { logger.LogError("Token cancelled for unknown reasons, exiting."); return ExitCode.GENERAL_FAILURE; } else { logger.LogInformation($"Stopping the webserver after the timeout of {Arguments.Timeout}"); return ExitCode.SUCCESS; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.StaticFiles; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using LogLevel = Microsoft.Extensions.Logging.LogLevel; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; using System.IO; namespace Microsoft.DotNet.XHarness.CLI.Commands; public class WebServer { internal static Task Start(IWebServerArguments arguments, ILogger logger, CancellationToken token, Func? onConsoleConnected = null) { var options = TestWebServerOptions.FromArguments(arguments); options.OnConsoleConnected = onConsoleConnected; return Start(options, logger, token); } internal static async Task Start(TestWebServerOptions webServerOptions, ILogger logger, CancellationToken token) { var urls = webServerOptions.UseHttps ? new string[] { "http://127.0.0.1:0", "https://127.0.0.1:0" } : new string[] { "http://127.0.0.1:0" }; var builder = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup() .UseKestrel() .UseUrls(urls); }) .ConfigureLogging(logging => { logging.AddConsole().AddFilter(null, LogLevel.Warning); }) .ConfigureServices((ctx, services) => { if (webServerOptions.UseCors) { services.AddCors(o => o.AddPolicy("AnyCors", builder => { builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .WithExposedHeaders("*"); })); } services.AddRouting(); services.AddLogging(); services.AddSingleton(logger); services.Configure(ctx.Configuration); services.Configure(options => { webServerOptions.CopyTo(options); }); }); if (webServerOptions.ContentRoot != null) { builder.UseContentRoot(webServerOptions.ContentRoot); } var host = builder.Build(); await host.StartAsync(token); var server = host.Services.GetService(); var addressFeature = server?.Features.Get(); var ipAddress = addressFeature? .Addresses .Where(a => a.StartsWith("http:")) .Select(a => new Uri(a)) .Select(uri => $"{uri.Host}:{uri.Port}") .FirstOrDefault(); var ipAddressSecure = addressFeature? .Addresses .Where(a => a.StartsWith("https:")) .Select(a => new Uri(a)) .Select(uri => $"{uri.Host}:{uri.Port}") .FirstOrDefault(); if (ipAddress == null || (webServerOptions.UseHttps && ipAddressSecure == null)) { throw new InvalidOperationException("Failed to determine web server's IP address or port"); } return new ServerURLs(ipAddress, ipAddressSecure); } private class TestWebServerStartup { private readonly IWebHostEnvironment _hostingEnvironment; public TestWebServerStartup(IWebHostEnvironment hostingEnvironment) { _hostingEnvironment = hostingEnvironment; } public void Configure(IApplicationBuilder app, IOptionsMonitor optionsAccessor) { var logger = app.ApplicationServices.GetRequiredService(); var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".wasm"] = "application/wasm"; provider.Mappings[".cjs"] = "text/javascript"; provider.Mappings[".mjs"] = "text/javascript"; foreach (var extn in new string[] { ".dll", ".pdb", ".dat", ".blat", ".webcil" }) { provider.Mappings[extn] = "application/octet-stream"; } var options = optionsAccessor.CurrentValue; if (options.UseCrossOriginPolicy) { app.Use((context, next) => { context.Response.Headers.Append("Cross-Origin-Embedder-Policy", "require-corp"); context.Response.Headers.Append("Cross-Origin-Opener-Policy", "same-origin"); return next(); }); } if (options.UseDefaultFiles) { app.UseDefaultFiles(new DefaultFilesOptions { FileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath) }); } app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath), ContentTypeProvider = provider, ServeUnknownFileTypes = true }); if (options.UseCors) { app.UseCors("AnyCors"); } app.UseWebSockets(); if (options.OnConsoleConnected != null) { app.UseRouter(router => { router.MapGet("/console", async context => { if (!context.WebSockets.IsWebSocketRequest) { context.Response.StatusCode = 400; return; } var socket = await context.WebSockets.AcceptWebSocketAsync(); await options.OnConsoleConnected(socket); }); }); } if (options.WebServerUploadResults) { app.UseRouter(router => { router.MapPost("/test-results", async context => { var xmlResultsFilePath = Path.Combine(options.OutputDirectory!, "testResults.xml"); await using var fileStream = File.Create(xmlResultsFilePath); await context.Request.BodyReader.CopyToAsync(fileStream); logger.LogInformation($"Stored {xmlResultsFilePath} results {fileStream.Position} bytes"); }); }); } foreach (var middleware in options.EchoServerMiddlewares) { app.UseMiddleware(middleware); logger.LogInformation($"Loaded {middleware.FullName} middleware"); } } } internal class TestWebServerOptions { public Func? OnConsoleConnected { get; set; } public IList EchoServerMiddlewares { get; set; } = new List(); public bool UseCors { get; set; } public bool UseHttps { get; set; } public bool UseCrossOriginPolicy { get; set; } public bool UseDefaultFiles { get; set; } public bool WebServerUploadResults { get; set; } public string? OutputDirectory { get; set; } public string? ContentRoot { get; set; } public void CopyTo(TestWebServerOptions otherOptions) { otherOptions.OnConsoleConnected = OnConsoleConnected; otherOptions.EchoServerMiddlewares = EchoServerMiddlewares; otherOptions.UseCors = UseCors; otherOptions.UseHttps = UseHttps; otherOptions.UseCrossOriginPolicy = UseCrossOriginPolicy; otherOptions.UseDefaultFiles = UseDefaultFiles; otherOptions.WebServerUploadResults = WebServerUploadResults; otherOptions.OutputDirectory = OutputDirectory; otherOptions.ContentRoot = ContentRoot; } public static TestWebServerOptions FromArguments(IWebServerArguments arguments) { TestWebServerOptions options = new(); options.UseCors = arguments.WebServerUseCors; options.UseHttps = arguments.WebServerUseHttps; options.UseCrossOriginPolicy = arguments.WebServerUseCrossOriginPolicy; options.UseDefaultFiles = arguments.WebServerUseDefaultFiles; options.WebServerUploadResults = arguments.WebServerUploadResults; options.OutputDirectory = arguments.OutputDirectory; foreach (var middlewareType in arguments.WebServerMiddlewarePathsAndTypes.GetLoadedTypes()) { options.EchoServerMiddlewares.Add(middlewareType); } return options; } } } public record ServerURLs(string Http, string? Https); ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/XHarnessCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.CLI.CommandArguments; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; using Mono.Options; namespace Microsoft.DotNet.XHarness.CLI.Commands; public abstract class XHarnessCommand : Command where T : IXHarnessCommandArguments { private readonly TargetPlatform _targetPlatform; private readonly bool _allowsExtraArgs; /// /// Will be printed in the header when help is invoked. /// Example: 'ios package [OPTIONS]' /// protected abstract string CommandUsage { get; } /// /// Will be printed in the header when help is invoked. /// Example: 'Allows to package DLLs into an app bundle' /// protected abstract string CommandDescription { get; } protected abstract T Arguments { get; } /// /// Service collection used to create dependencies. /// protected IServiceCollection Services { get; } protected XHarnessCommand( TargetPlatform targetPlatform, string name, bool allowsExtraArgs, IServiceCollection services, string? help = null) : base(name, help) { _allowsExtraArgs = allowsExtraArgs; _targetPlatform = targetPlatform; Services = services; } /// /// Contains all arguments after the verbatim "--" argument. /// /// Example: /// > xharness ios test --arg1=value1 -- --foo -v /// Will contain [ "foo", "v" ] /// protected IEnumerable PassThroughArguments { get; private set; } = Enumerable.Empty(); /// /// Extra arguments parsed to the command (if the command allows it). /// protected IEnumerable ExtraArguments { get; private set; } = Enumerable.Empty(); public sealed override int Invoke(IEnumerable arguments) { var commandArguments = Arguments.GetCommandArguments(); var options = new OptionSet(); foreach (var arg in commandArguments) { options.Add(arg.Prototype, arg.Description, arg.Action); } using var parseFactory = CreateLoggerFactory(Arguments.Verbosity); var parseLogger = parseFactory.CreateLogger(Name); try { var regularArguments = arguments.TakeWhile(arg => arg != Program.VerbatimArgumentPlaceholder); if (regularArguments.Count() < arguments.Count()) { PassThroughArguments = arguments.Skip(regularArguments.Count() + 1); arguments = regularArguments; } var extra = options.Parse(arguments); if (extra.Count > 0) { if (_allowsExtraArgs) { ExtraArguments = extra; } else { throw new ArgumentException($"Unknown arguments: {string.Join(" ", extra)}"); } } if (Arguments.ShowHelp) { Console.WriteLine("usage: " + CommandUsage + Environment.NewLine + Environment.NewLine + CommandDescription + Environment.NewLine); options.WriteOptionDescriptions(Console.Out); return (int)ExitCode.HELP_SHOWN; } Arguments.Validate(); } catch (ArgumentException e) { parseLogger.LogError(e.Message); if (Arguments.ShowHelp) { options.WriteOptionDescriptions(Console.Out); } return (int)ExitCode.INVALID_ARGUMENTS; } catch (Exception e) { parseLogger.LogCritical("Unexpected failure argument: {error}", e); return (int)ExitCode.GENERAL_FAILURE; } // Should we and where output the diagnostics data? string? diagnosticsPath = Arguments.Diagnostics.Value ?? Environment.GetEnvironmentVariable(EnvironmentVariables.Names.DIAGNOSTICS_PATH); try { using var factory = CreateLoggerFactory(Arguments.Verbosity); ILogger logger = factory.CreateLogger(Name); var diagnostics = new CommandDiagnostics(logger, _targetPlatform, Name); Services.TryAddSingleton(logger); Services.TryAddSingleton(diagnostics); var result = InvokeInternal(logger).GetAwaiter().GetResult(); diagnostics.ExitCode = result; // Save diagnostic data into a file if (result != ExitCode.HELP_SHOWN && !string.IsNullOrEmpty(diagnosticsPath)) { diagnostics.SaveToJsonFile(diagnosticsPath); } return (int)result; } catch (Exception e) { parseLogger.LogCritical(e.ToString()); return (int)ExitCode.GENERAL_FAILURE; } } protected abstract Task InvokeInternal(ILogger logger); private static ILoggerFactory CreateLoggerFactory(LogLevel verbosity) => LoggerFactory.Create(builder => { builder .AddConsoleFormatter(options => { options.ColorBehavior = EnvironmentVariables.IsTrue(EnvironmentVariables.Names.DISABLE_COLOR_OUTPUT) ? LoggerColorBehavior.Disabled : LoggerColorBehavior.Default; options.TimestampFormat = EnvironmentVariables.IsTrue(EnvironmentVariables.Names.LOG_TIMESTAMPS) ? "[HH:mm:ss] " : null!; }) .AddConsole(options => options.FormatterName = XHarnessConsoleLoggerFormatter.FormatterName) .SetMinimumLevel(verbosity); }); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/XHarnessHelpCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.Commands.Apple; using Microsoft.DotNet.XHarness.CLI.Commands.Apple.Simulators; using Microsoft.DotNet.XHarness.CLI.Commands.Wasm; using Microsoft.DotNet.XHarness.CLI.Commands.Wasi; using Microsoft.DotNet.XHarness.Common.CLI; using Mono.Options; namespace Microsoft.DotNet.XHarness.CLI.Commands; internal class XHarnessHelpCommand : HelpCommand { public override int Invoke(IEnumerable arguments) { string[] args = arguments.ToArray(); if (args.Length == 0) { base.Invoke(arguments); return (int)ExitCode.HELP_SHOWN; } var command = args[0].ToLowerInvariant(); string? subCommand = null; if (args.Length >= 2) { subCommand = args[1]; } // Unfortunately, CommandSet.NestedCommandSets is not visible and we cannot go through // the command tree dynamically to any depth. switch (command) { case "android": PrintCommandHelp(new AndroidCommandSet(), subCommand); break; case "apple": #if !DEBUG if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { #endif var simulatorsSubset = new SimulatorsCommandSet(); if (subCommand == simulatorsSubset.Suite) { PrintCommandHelp(simulatorsSubset, args.ElementAtOrDefault(2)); } else { PrintCommandHelp(new AppleCommandSet(), subCommand); } #if !DEBUG } else { Console.WriteLine($"Command '{command}' could be run on OSX only."); } #endif break; case "wasm": PrintCommandHelp(new WasmCommandSet(), subCommand); break; case "wasi": PrintCommandHelp(new WasiCommandSet(), subCommand); break; default: Console.WriteLine($"No help available for command '{command}'. Allowed commands are 'apple', 'wasm', 'wasi' and 'android'"); break; } return (int)ExitCode.HELP_SHOWN; } private static void PrintCommandHelp(CommandSet commandSet, string? subcommand) { if (subcommand != null) { var command = commandSet.Where(c => c.Name == subcommand).FirstOrDefault(); if (command != null) { command.Invoke(new string[] { "--help" }); return; } Console.WriteLine($"Unknown sub-command '{subcommand}'.{Environment.NewLine}"); } Console.WriteLine("All supported sub-commands:"); commandSet.Run(new string[] { "help" }); Console.WriteLine($"{Environment.NewLine}Run 'xharness {commandSet.Suite} {{command}} --help' for more details"); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Commands/XHarnessVersionCommand.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using Mono.Options; namespace Microsoft.DotNet.XHarness.CLI.Commands; internal class XHarnessVersionCommand : Command { public XHarnessVersionCommand() : base("version") { } public override int Invoke(IEnumerable arguments) { var version = GetAssemblyVersion(); if (!arguments.Contains("-v")) { Console.WriteLine(version.ProductVersion); return 0; } // Print the name of the tool and the version number unix style // Example: // Apple clang version 11.0.3 (clang-1103.0.32.29) // Target: x86_64-apple-darwin19.4.0 // InstalledDir: /Applications/Xcode114.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin Console.WriteLine($"XHarness version {version.ProductVersion} ({version.OriginalFilename})"); Console.WriteLine($"InstalledDir: {version.FileName}"); return 0; } public static FileVersionInfo GetAssemblyVersion() => FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location); } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Microsoft.DotNet.XHarness.CLI.csproj ================================================  Exe $(XHarnessNetTFMs) true true xharness Major $(NoWarn);CS8002 $(BaseIntermediateOutputPath)/mlaunch false 32.0.0 $(AssetsBaseUri)/android/windows-android-platform-tools_r$(AndroidSdkVersion).zip windows-android-platform-tools.zip $(AssetsBaseUri)/android/linux-android-platform-tools_r$(AndroidSdkVersion).zip linux-android-platform-tools.zip $(AssetsBaseUri)/android/macos-android-platform-tools_r$(AndroidSdkVersion).zip macos-android-platform-tools.zip True True Strings.resx true runtimes/any/native/adb/windows PreserveNewest true runtimes/any/native/adb/linux PreserveNewest true runtimes/any/native/adb/macos PreserveNewest true runtimes/any/native/mlaunch PreserveNewest ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Program.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Linq; using System.Runtime.InteropServices; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.AndroidHeadless; using Microsoft.DotNet.XHarness.CLI.Commands; using Microsoft.DotNet.XHarness.CLI.Commands.Apple; using Microsoft.DotNet.XHarness.CLI.Commands.Wasm; using Microsoft.DotNet.XHarness.CLI.Commands.Wasi; using Microsoft.DotNet.XHarness.Common.CLI; using Mono.Options; namespace Microsoft.DotNet.XHarness.CLI; public static class Program { /// /// The verbatim "--" argument used for pass-through args is removed by Mono.Options when parsing CommandSets, /// so in Program.cs, we temporarily replace it with this string and then recognize it back here. /// public const string VerbatimArgumentPlaceholder = "[[%verbatim_argument%]]"; public static int Main(string[] args) { bool shouldOutput = !IsOutputSensitive(args); if (shouldOutput) { Console.WriteLine( $"[{XHarnessVersionCommand.GetAssemblyVersion().ProductVersion}] " + "XHarness command issued: " + string.Join(' ', args)); } if (args.Length > 0) { #if !DEBUG if (args[0] == "apple" && !RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { // Otherwise the command would just not be found Console.Error.WriteLine("The 'apple' command is not available on non-OSX platforms!"); return (int)ExitCode.INVALID_ARGUMENTS; } #endif // Mono.Options wouldn't allow "--" so we will temporarily rename it and parse it ourselves later args = args.Select(a => a == "--" ? VerbatimArgumentPlaceholder : a).ToArray(); } var commands = GetXHarnessCommandSet(); int result = commands.Run(args); string? exitCodeName = null; if (args.Length > 0 && result != 0 && Enum.IsDefined(typeof(ExitCode), result)) { exitCodeName = $" ({(ExitCode)result})"; } if (shouldOutput) { Console.WriteLine($"XHarness exit code: {result}{exitCodeName}"); } return result; } public static CommandSet GetXHarnessCommandSet() { #pragma warning disable IDE0028 // Simplify collection initialization for DEBUG var commandSet = new CommandSet("xharness"); #pragma warning restore IDE0028 // Simplify collection initialization for DEBUG #if !DEBUG if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { commandSet.Add(new AppleCommandSet()); } #else commandSet.Add(new AppleCommandSet()); #endif commandSet.Add(new AndroidCommandSet()); commandSet.Add(new AndroidHeadlessCommandSet()); commandSet.Add(new WasmCommandSet()); commandSet.Add(new WasiCommandSet()); commandSet.Add(new XHarnessHelpCommand()); commandSet.Add(new XHarnessVersionCommand()); return commandSet; } /// /// Returns true when the command outputs data suitable for parsing and we should keep the output clean. /// private static bool IsOutputSensitive(string[] args) { if (args.Length > 0 && args[0] == "version") { return true; } if (args.Length < 2 || args.Contains("--help") || args.Contains("-h")) { return false; } var platform = args[0]; var command = args[1]; return platform switch { "apple" => command switch { "device" => true, "state" => args.Contains("--json"), "mlaunch" => true, _ => false, }, "android" => command switch { "device" => true, "state" => args.Contains("--json"), "adb" => true, _ => false, }, "android-headless" => command switch { "device" => true, "state" => args.Contains("--json"), "adb" => true, _ => false, }, _ => false, }; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Properties/AssemblyInfo.cs ================================================ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.DotNet.XHarness.CLI.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Resources/Strings.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Microsoft.DotNet.XHarness.CLI.Resources { using System; /// /// A strongly-typed resource class, for looking up localized strings, etc. /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Strings { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Strings() { } /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.DotNet.XHarness.CLI.Resources.Strings", typeof(Strings).Assembly); resourceMan = temp; } return resourceMan; } } /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// /// Looks up a localized string similar to Installs, runs and uninstalls a given iOS/tvOS/watchOS/xrOS/MacCatalyst test application bundle containing TestRunner in a target device/simulator. /// public static string Apple_Test_Description { get { return ResourceManager.GetString("Apple_Test_Description", resourceCulture); } } /// /// Looks up a localized string similar to apple test --app=... --output-directory=... --target=... [OPTIONS] [-- [RUNTIME ARGUMENTS]]. /// public static string Apple_Test_Usage { get { return ResourceManager.GetString("Apple_Test_Usage", resourceCulture); } } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/Resources/Strings.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 apple test --app=... --output-directory=... --target=... [OPTIONS] [-- [RUNTIME ARGUMENTS]] Installs, runs and uninstalls a given iOS/tvOS/watchOS/xrOS/MacCatalyst test application bundle containing TestRunner in a target device/simulator. ================================================ FILE: src/Microsoft.DotNet.XHarness.CLI/XHarnessConsoleLoggerFormatter.cs ================================================ // Licensed to the.NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.IO; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Console; using Microsoft.Extensions.Options; namespace Microsoft.DotNet.XHarness.CLI; /// /// Copied over from SimpleConsoleFormatter. Leaves out the logger name and new line, turning /// info: test[0] /// Log message /// Second line of the message /// /// into /// /// info: Log message /// Second line of the message /// /// Only using SimpleConsoleFormatterOptions.SingleLine didn't help because multi-line messages /// were put together on a single line so things like stack traces of exceptions were unreadable. /// /// See https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs /// internal class XHarnessConsoleLoggerFormatter : ConsoleFormatter { private const string LoglevelPadding = ": "; private const string DefaultForegroundColor = "\x1B[39m\x1B[22m"; // reset to default foreground color private const string DefaultBackgroundColor = "\x1B[49m"; // reset to the background color public const string FormatterName = "xharness"; private readonly SimpleConsoleFormatterOptions _options; private readonly string _messagePadding; private readonly string _newLineWithMessagePadding; public XHarnessConsoleLoggerFormatter(IOptionsMonitor options) : base(FormatterName) { _options = options.CurrentValue; _messagePadding = new string(' ', GetLogLevelString(LogLevel.Information).Length + LoglevelPadding.Length + (_options.TimestampFormat?.Length ?? 0)); _newLineWithMessagePadding = Environment.NewLine + _messagePadding; } public override void Write(in LogEntry logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter) { if (logEntry.Formatter == null) { return; } var message = logEntry.Formatter(logEntry.State, logEntry.Exception); if (logEntry.Exception == null && message == null) { return; } LogLevel logLevel = logEntry.LogLevel; var logLevelColors = GetLogLevelConsoleColors(logLevel); var logLevelString = GetLogLevelString(logLevel); if (_options.TimestampFormat != null) { var timestamp = DateTimeOffset.Now.ToString(_options.TimestampFormat); textWriter.Write(timestamp); } WriteColoredMessage(textWriter, logLevelString, logLevelColors.Background, logLevelColors.Foreground); textWriter.Write(LoglevelPadding); WriteMessage(textWriter, message, false); // Example: // System.InvalidOperationException // at Namespace.Class.Function() in File:line X if (logEntry.Exception != null) { // exception message WriteMessage(textWriter, logEntry.Exception.ToString()); } } private void WriteMessage(TextWriter textWriter, string message, bool includePadding = true) { if (message == null) { return; } if (includePadding) { textWriter.Write(_messagePadding); } textWriter.WriteLine(message.Replace(Environment.NewLine, _newLineWithMessagePadding)); } private static string GetLogLevelString(LogLevel logLevel) => logLevel switch { LogLevel.Trace => "trce", LogLevel.Debug => "dbug", LogLevel.Information => "info", LogLevel.Warning => "warn", LogLevel.Error => "fail", LogLevel.Critical => "crit", _ => throw new ArgumentOutOfRangeException(nameof(logLevel)) }; private (ConsoleColor? Foreground, ConsoleColor? Background) GetLogLevelConsoleColors(LogLevel logLevel) { if (_options.ColorBehavior == LoggerColorBehavior.Disabled) { return (null, null); } // We must explicitly set the background color if we are setting the foreground color, // since just setting one can look bad on the users console. return logLevel switch { LogLevel.Trace => (ConsoleColor.Gray, ConsoleColor.Black), LogLevel.Debug => (ConsoleColor.Gray, ConsoleColor.Black), LogLevel.Information => (ConsoleColor.DarkGreen, ConsoleColor.Black), LogLevel.Warning => (ConsoleColor.Yellow, ConsoleColor.Black), LogLevel.Error => (ConsoleColor.Black, ConsoleColor.DarkRed), LogLevel.Critical => (ConsoleColor.White, ConsoleColor.DarkRed), _ => (null, null) }; } private static void WriteColoredMessage(TextWriter textWriter, string message, ConsoleColor? background, ConsoleColor? foreground) { // Order: backgroundcolor, foregroundcolor, Message, reset foregroundcolor, reset backgroundcolor if (background.HasValue) { textWriter.Write(GetBackgroundColorEscapeCode(background.Value)); } if (foreground.HasValue) { textWriter.Write(GetForegroundColorEscapeCode(foreground.Value)); } textWriter.Write(message); if (foreground.HasValue) { textWriter.Write(DefaultForegroundColor); // reset to default foreground color } if (background.HasValue) { textWriter.Write(DefaultBackgroundColor); // reset to the background color } } private static string GetForegroundColorEscapeCode(ConsoleColor color) => color switch { ConsoleColor.Black => "\x1B[30m", ConsoleColor.DarkRed => "\x1B[31m", ConsoleColor.DarkGreen => "\x1B[32m", ConsoleColor.DarkYellow => "\x1B[33m", ConsoleColor.DarkBlue => "\x1B[34m", ConsoleColor.DarkMagenta => "\x1B[35m", ConsoleColor.DarkCyan => "\x1B[36m", ConsoleColor.Gray => "\x1B[37m", ConsoleColor.Red => "\x1B[1m\x1B[31m", ConsoleColor.Green => "\x1B[1m\x1B[32m", ConsoleColor.Yellow => "\x1B[1m\x1B[33m", ConsoleColor.Blue => "\x1B[1m\x1B[34m", ConsoleColor.Magenta => "\x1B[1m\x1B[35m", ConsoleColor.Cyan => "\x1B[1m\x1B[36m", ConsoleColor.White => "\x1B[1m\x1B[37m", _ => DefaultForegroundColor // default foreground color }; private static string GetBackgroundColorEscapeCode(ConsoleColor color) => color switch { ConsoleColor.Black => "\x1B[40m", ConsoleColor.DarkRed => "\x1B[41m", ConsoleColor.DarkGreen => "\x1B[42m", ConsoleColor.DarkYellow => "\x1B[43m", ConsoleColor.DarkBlue => "\x1B[44m", ConsoleColor.DarkMagenta => "\x1B[45m", ConsoleColor.DarkCyan => "\x1B[46m", ConsoleColor.Gray => "\x1B[47m", _ => DefaultBackgroundColor // Use default background color }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/CLI/EnvironmentVariables.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.Common.CLI; public static class EnvironmentVariables { public static class Names { public const string DISABLE_COLOR_OUTPUT = "XHARNESS_DISABLE_COLORED_OUTPUT"; public const string LOG_TIMESTAMPS = "XHARNESS_LOG_WITH_TIMESTAMPS"; public const string MLAUNCH_PATH = "XHARNESS_MLAUNCH_PATH"; public const string DIAGNOSTICS_PATH = "XHARNESS_DIAGNOSTICS_PATH"; } public static bool IsTrue(string varName) => Environment.GetEnvironmentVariable(varName)?.ToLower().Equals("true") ?? false; } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/CLI/ExitCode.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.Common.CLI; /// /// Exit codes to use for common failure reasons; if you add a new exit code, add it here and use the enum. /// The first part conforms with xUnit: https://xunit.net/docs/getting-started/netfx/visual-studio /// public enum ExitCode { /// /// The tests ran successfully /// SUCCESS = 0, /// /// One or more of the tests failed /// TESTS_FAILED = 1, /// /// The help page was shown /// Either because it was requested, or because the user did not provide any command line arguments /// HELP_SHOWN = 2, /// /// There was a problem with one of the command line options /// INVALID_ARGUMENTS = 3, /// /// There was a problem loading one or more of the test packages /// PACKAGE_NOT_FOUND = 4, /// /// Time out based on the --timeout settings /// TIMED_OUT = 70, /// /// Generic code for cases where we couldn't determine the exact cause /// GENERAL_FAILURE = 71, /// /// App installation failed /// PACKAGE_INSTALLATION_FAILURE = 78, /// /// Failed to open/parse Info.plist inside of the app bundle /// FAILED_TO_GET_BUNDLE_INFO = 79, /// /// The app was launched but we never heard from it and similar cases /// APP_CRASH = 80, /// /// XHarness failed to find a suitable target for the test /// DEVICE_NOT_FOUND = 81, /// /// Various scenarios that depend on an exit code which was not returned /// RETURN_CODE_NOT_SET = 82, /// /// An error occurred when trying to launch the application /// APP_LAUNCH_FAILURE = 83, /// /// Failed to retrieve a file from the Android device/emulator /// DEVICE_FILE_COPY_FAILURE = 84, /// /// Time outs happening during the installation phase (or install command) /// PACKAGE_INSTALLATION_TIMEOUT = 86, /// /// Apple app is not signed, provisioning profile is missing and similar /// APP_NOT_SIGNED = 87, /// /// Failed to start simulator (happens every now and then on MacOS mostly) /// SIMULATOR_FAILURE = 88, /// /// Hardware device is in some corrupted state, or just locked screen /// DEVICE_FAILURE = 89, /// /// This timeout occurs when the Apple app has been launched but hasn't /// connected yet over TCP and --launch-timeout expires /// APP_LAUNCH_TIMEOUT = 90, /// /// Failure when calling ADB /// ADB_FAILURE = 91, /// /// Failure when TCP tunnel between XHarness and the device fail /// or the device cannot connect to TCP tunnel /// TCP_CONNECTION_FAILED = 92 } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/CLI/NoDeviceFoundException.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Runtime.Serialization; namespace Microsoft.DotNet.XHarness.Common.CLI; public class NoDeviceFoundException : Exception { public NoDeviceFoundException() { } public NoDeviceFoundException(string message) : base(message) { } public NoDeviceFoundException(string message, Exception innerException) : base(message, innerException) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/CommandDiagnostics.cs ================================================ // Licensed to the.NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.Common; public interface IDiagnosticsData { ExitCode ExitCode { get; set; } /// /// Target the user/app specified when executing the command. /// string? Target { get; set; } /// /// OS version of the actual test target (simulator, device) that was used for the run. /// string? TargetOS { get; set; } /// /// Name of the used device. /// string? Device { get; set; } /// /// True when the target is a real HW device, false for simulators, maccatalyst.. /// bool? IsDevice { get; set; } /// /// Files produced during the command execution. /// IList Files { get; } } /// /// Class responsible for gathering of diagnostics data and saving them into a file. /// public class CommandDiagnostics : IDiagnosticsData { private readonly ILogger _logger; private readonly Stopwatch _timer = Stopwatch.StartNew(); public string Platform { get; } public string Command { get; } [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public ExitCode ExitCode { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Target { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? TargetOS { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Device { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? IsDevice { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public IList Files { get; } = new List(); public int Duration => (int)Math.Round(_timer.Elapsed.TotalSeconds); public CommandDiagnostics(ILogger logger, TargetPlatform platform, string command) { _logger = logger; Command = command; Platform = platform switch { TargetPlatform.Android => "android", TargetPlatform.Apple => "apple", TargetPlatform.WASM => "wasm", TargetPlatform.WASI => "wasi", _ => throw new ArgumentOutOfRangeException(nameof(platform)), }; } /// /// Saves the data to a JSON file as an object in a JSON array. /// If the file exists already, it is appended at the end of the array. /// /// JSON file where to save the data public void SaveToJsonFile(string targetFile) { _timer.Stop(); var options = new JsonSerializerOptions { #if DEBUG WriteIndented = true, #else WriteIndented = false, #endif PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; _logger.LogDebug("Saving diagnostics data to '{path}'", targetFile); try { // Either append current data to the JSON array or create a new file if (File.Exists(targetFile)) { var data = JsonDocument.Parse(File.ReadAllText(targetFile)); var writerOptions = new JsonWriterOptions { Indented = options.WriteIndented, }; using var fileStream = new FileStream(targetFile, FileMode.Create, FileAccess.Write); using var jsonWriter = new Utf8JsonWriter(fileStream, writerOptions); jsonWriter.WriteStartArray(); // Copy the existing elements without going into details of what they are var newData = new List(); var enumerator = data.RootElement.EnumerateArray(); while (enumerator.MoveNext()) { enumerator.Current.WriteTo(jsonWriter); } // New element JsonSerializer.Serialize(jsonWriter, this, options); jsonWriter.WriteEndArray(); } else { var data = new[] { this }; string json = JsonSerializer.Serialize(data, options); File.WriteAllText(targetFile, json); } } catch (Exception e) { _logger.LogError("Failed to save diagnostics data to '{pathToFile}': {error}", targetFile, e); } } } /// /// Represents a file produced during a command execution. /// public class DiagnosticsFile { public string Name { get; set; } = string.Empty; public string Type { get; set; } = string.Empty; [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Path { get; set; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Execution/IMacOSProcessManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.Common.Execution; public interface IMacOSProcessManager : IProcessManager { string XcodeRoot { get; } public Version XcodeVersion { get; } Task ExecuteXcodeCommandAsync( string executable, IList args, ILog log, TimeSpan timeout, CancellationToken cancellationToken = default); Task ExecuteXcodeCommandAsync( string executable, IList args, ILog log, ILog stdoutLog, ILog stderrLog, TimeSpan timeout, CancellationToken cancellationToken = default); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Execution/IProcessManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.Common.Execution; public class ProcessExecutionResult { public bool TimedOut { get; set; } public int ExitCode { get; set; } public bool Succeeded => !TimedOut && ExitCode == 0; } /// /// Interface that helps to manage processes /// public interface IProcessManager { Task ExecuteCommandAsync( string filename, IList args, ILog log, TimeSpan timeout, Dictionary? environmentVariables = null, CancellationToken? cancellationToken = null); Task ExecuteCommandAsync( string filename, IList args, ILog log, ILog stdoutLog, ILog stderrLog, TimeSpan timeout, Dictionary? environmentVariables = null, CancellationToken? cancellationToken = null); Task RunAsync( Process process, ILog log, TimeSpan? timeout = null, Dictionary? environmentVariables = null, CancellationToken? cancellationToken = null, bool? diagnostics = null); Task RunAsync( Process process, ILog log, ILog stdoutLog, ILog stderrLog, TimeSpan? timeout = null, Dictionary? environmentVariables = null, CancellationToken? cancellationToken = null, bool? diagnostics = null); Task KillTreeAsync(Process process, ILog log, bool? diagnostics = true); Task KillTreeAsync(int pid, ILog log, bool? diagnostics = true); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Execution/LinuxProcessManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; namespace Microsoft.DotNet.XHarness.Common.Execution; public class LinuxProcessManager : UnixProcessManager { [DllImport("libc")] private static extern int kill(int pid, int sig); protected override int Kill(int pid, int sig) => kill(pid, sig); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Execution/MacOSProcessManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Xml; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.Common.Execution; public class MacOSProcessManager : UnixProcessManager, IMacOSProcessManager { #region Private variables private static readonly Lazy s_autoDetectedXcodeRoot = new(DetectXcodePath, LazyThreadSafetyMode.PublicationOnly); private readonly string? _xcodeRoot; private Version? _xcode_version; #endregion public MacOSProcessManager(string? xcodeRoot = null) { _xcodeRoot = xcodeRoot; } #region IMacOSProcessManager implementation public string XcodeRoot => _xcodeRoot ?? s_autoDetectedXcodeRoot.Value; public Version XcodeVersion { get { if (_xcode_version == null) { var doc = new XmlDocument(); var plistPath = Path.Combine(XcodeRoot, "Contents", "version.plist"); try { doc.Load(plistPath); _xcode_version = Version.Parse(doc.SelectSingleNode("//key[text() = 'CFBundleShortVersionString']/following-sibling::string")?.InnerText ?? throw new Exception("Failed to find the CFBundleShortVersionString property")); } catch (IOException e) { throw new Exception( $"Failed to find Xcode! Version.plist missing at {plistPath}. " + "Please make sure xcode-select is set up, or the path to Xcode is supplied as an argument.", e); } } return _xcode_version; } } public Task ExecuteXcodeCommandAsync( string executable, IList args, ILog log, ILog stdoutLog, ILog stderrLog, TimeSpan timeout, CancellationToken cancellationToken = default) { var filename = Path.Combine(XcodeRoot, "Contents", "Developer", "usr", "bin", executable); return ExecuteCommandAsync(filename, args, log, stdoutLog, stderrLog, timeout: timeout, cancellationToken: cancellationToken); } public Task ExecuteXcodeCommandAsync(string executable, IList args, ILog log, TimeSpan timeout, CancellationToken cancellationToken = default) => ExecuteXcodeCommandAsync(executable, args, log, log, log, timeout, cancellationToken); #endregion [DllImport("/usr/lib/libc.dylib")] private static extern int kill(int pid, int sig); #region Override methods protected override int Kill(int pid, int sig) => kill(pid, sig); #endregion #region Static methods public static string DetectXcodePath() { using var process = new Process(); process.StartInfo.FileName = "xcode-select"; process.StartInfo.Arguments = "-p"; var log = new MemoryLog(); var stdout = new MemoryLog() { Timestamp = false }; var stderr = new ConsoleLog(); var timeout = TimeSpan.FromSeconds(30); var result = RunAsyncInternal( process, log, stdout, stderr, (pid, signal) => _ = kill(pid, signal), (log, pid) => GetChildProcessIdsInternal(log, pid), timeout) .GetAwaiter().GetResult(); if (!result.Succeeded) { throw new Exception("Failed to detect Xcode path from xcode-select!"); } // Something like /Applications/Xcode114.app/Contents/Developers var xcodeRoot = stdout.ToString().Trim(); if (string.IsNullOrEmpty(xcodeRoot)) { throw new Exception("Failed to detect Xcode path from xcode-select!"); } // We need /Applications/Xcode114.app only // should never be null, if it is return an "" return Path.GetDirectoryName(Path.GetDirectoryName(xcodeRoot)) ?? string.Empty; } public static string DetectMlaunchPath() { string? pathFromEnv = Environment.GetEnvironmentVariable(EnvironmentVariables.Names.MLAUNCH_PATH); if (!string.IsNullOrEmpty(pathFromEnv)) { return pathFromEnv; } // This path is where mlaunch is when the .NET tool is extracted from the .nupkg var assemblyPath = Path.GetDirectoryName(System.Reflection.Assembly.GetAssembly(typeof(MacOSProcessManager))?.Location)!; return Path.Combine(assemblyPath, "..", "..", "..", "runtimes", "any", "native", "mlaunch", "bin", "mlaunch"); } #endregion } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Execution/ProcessManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.Common.Utilities; namespace Microsoft.DotNet.XHarness.Common.Execution; public abstract class ProcessManager : IProcessManager { #region Abstract methods protected abstract int Kill(int pid, int sig); protected abstract List GetChildProcessIds(ILog log, int pid); #endregion #region IProcessManager implementation public async Task ExecuteCommandAsync(string filename, IList args, ILog log, TimeSpan timeout, Dictionary? environmentVariables = null, CancellationToken? cancellationToken = null) => await ExecuteCommandAsync(filename, args, log, log, log, timeout, environmentVariables, cancellationToken); public async Task ExecuteCommandAsync(string filename, IList args, ILog log, ILog stdout, ILog stderr, TimeSpan timeout, Dictionary? environmentVariables = null, CancellationToken? cancellationToken = null) { using var p = new Process(); p.StartInfo.FileName = filename ?? throw new ArgumentNullException(nameof(filename)); p.StartInfo.Arguments = StringUtils.FormatArguments(args); return await RunAsync(p, log, stdout, stderr, timeout, environmentVariables, cancellationToken); } public Task RunAsync( Process process, ILog log, TimeSpan? timeout = null, Dictionary? environmentVariables = null, CancellationToken? cancellationToken = null, bool? diagnostics = null) => RunAsync(process, log, log, log, timeout, environmentVariables, cancellationToken, diagnostics); public Task RunAsync( Process process, ILog log, ILog stdout, ILog stderr, TimeSpan? timeout = null, Dictionary? environmentVariables = null, CancellationToken? cancellationToken = null, bool? diagnostics = null) => RunAsyncInternal(process, log, stdout, stderr, timeout, environmentVariables, cancellationToken, diagnostics); public Task KillTreeAsync(Process process, ILog log, bool? diagnostics = true) => KillTreeAsync(process.Id, log, diagnostics); public Task KillTreeAsync(int pid, ILog log, bool? diagnostics = true) => KillTreeAsync(pid, log, (pid, signal) => Kill(pid, signal), (log, pid) => GetChildProcessIds(log, pid), diagnostics); protected static async Task KillTreeAsync( int pid, ILog log, Action kill, Func> getChildProcessIds, bool? diagnostics = true) { log.WriteLine($"Killing process tree of {pid}..."); var pids = getChildProcessIds(log, pid); log.WriteLine($"Pids to kill: {string.Join(", ", pids.Select((v) => v.ToString()).ToArray())}"); if (diagnostics == true) { foreach (var pidToDiagnose in pids) { log.WriteLine($"Running lldb diagnostics for pid {pidToDiagnose}"); var template = Path.GetTempFileName(); try { var commands = new StringBuilder(); using (var dbg = new Process()) { commands.AppendLine($"process attach --pid {pidToDiagnose}"); commands.AppendLine("thread list"); commands.AppendLine("thread backtrace all"); commands.AppendLine("detach"); commands.AppendLine("quit"); dbg.StartInfo.FileName = "lldb"; dbg.StartInfo.Arguments = StringUtils.FormatArguments("--source", template); File.WriteAllText(template, commands.ToString()); log.WriteLine($"Printing backtrace for pid={pidToDiagnose}"); await RunAsyncInternal( process: dbg, log: new NullLog(), stdout: log, stderr: log, kill, getChildProcessIds, timeout: TimeSpan.FromSeconds(20), diagnostics: false); } } catch (Win32Exception e) when (e.NativeErrorCode == 2) { log.WriteLine("lldb was not found, skipping diagnosis.."); } catch (Exception e) { log.WriteLine("Failed to diagnose the process using lldb:" + Environment.NewLine + e); } finally { try { File.Delete(template); } catch (Exception e) { log.WriteLine(e.Message); } } } } // Send SIGABRT since that produces a crash report // lldb may fail to attach to system processes, but crash reports will still be produced with potentially helpful stack traces. for (int i = 0; i < pids.Count; i++) { kill(pids[i], 6); } // send kill -9 anyway as a last resort for (int i = 0; i < pids.Count; i++) { kill(pids[i], 9); } } protected Task RunAsyncInternal( Process process, ILog log, ILog stdout, ILog stderr, TimeSpan? timeout = null, Dictionary? environmentVariables = null, CancellationToken? cancellationToken = null, bool? diagnostics = null) => RunAsyncInternal( process, log, stdout, stderr, (pid, signal) => Kill(pid, signal), (log, pid) => GetChildProcessIds(log, pid), timeout, environmentVariables, cancellationToken, diagnostics); protected static async Task RunAsyncInternal( Process process, ILog log, ILog stdout, ILog stderr, Action kill, Func> getChildProcessIds, TimeSpan? timeout = null, Dictionary? environmentVariables = null, CancellationToken? cancellationToken = null, bool? diagnostics = null) { var stdoutCompletion = new TaskCompletionSource(); var stderrCompletion = new TaskCompletionSource(); var result = new ProcessExecutionResult(); process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardOutput = true; // Make cute emojiis show up as cute emojiis in the output instead of ugly text symbols! process.StartInfo.StandardOutputEncoding = Encoding.UTF8; process.StartInfo.StandardErrorEncoding = Encoding.UTF8; process.StartInfo.UseShellExecute = false; if (environmentVariables != null) { foreach (var kvp in environmentVariables) { if (kvp.Value == null) { process.StartInfo.EnvironmentVariables.Remove(kvp.Key); } else { process.StartInfo.EnvironmentVariables[kvp.Key] = kvp.Value; } } } process.OutputDataReceived += (sender, e) => { if (e.Data != null) { lock (stdout) { stdout.WriteLine(e.Data); stdout.Flush(); } } else { stdoutCompletion.TrySetResult(true); } }; process.ErrorDataReceived += (sender, e) => { if (e.Data != null) { lock (stderr) { stderr.WriteLine(e.Data); stderr.Flush(); } } else { stderrCompletion.TrySetResult(true); } }; var sb = new StringBuilder(); if (process.StartInfo.Arguments.Any ()) { sb.Append($"Running {StringUtils.Quote(process.StartInfo.FileName)} {StringUtils.FormatArguments(process.StartInfo.ArgumentList)}"); } else { sb.Append($"Running {StringUtils.Quote(process.StartInfo.FileName)} {process.StartInfo.Arguments}"); } if (process.StartInfo.EnvironmentVariables != null) { var currentEnvironment = ToDictionary(Environment.GetEnvironmentVariables()); var processEnvironment = ToDictionary(process.StartInfo.EnvironmentVariables); var allVariables = currentEnvironment.Keys.Union(processEnvironment.Keys).Distinct(); bool headerShown = false; foreach (var variable in allVariables) { if (variable == null) { continue; } currentEnvironment.TryGetValue(variable, out var a); processEnvironment.TryGetValue(variable, out var b); if (a != b) { if (!headerShown) { sb.AppendLine().Append("With env vars: "); headerShown = true; } sb.AppendLine().Append($" {variable} = '{StringUtils.Quote(b)}'"); } } } // Separate process calls in logs log.WriteLine(string.Empty); log.WriteLine(sb.ToString()); process.Start(); var pid = process.Id; process.BeginErrorReadLine(); process.BeginOutputReadLine(); cancellationToken?.Register(() => { var hasExited = false; try { hasExited = process.HasExited; } catch (InvalidOperationException e) { // Process.HasExited can sometimes throw exceptions, so // just ignore those and to be safe treat it as the // process didn't exit (the safe option being to not leave // processes behind). stderr.WriteLine($"Process {pid} already exited or busy: {e.Message}"); } catch (Exception e) { stderr.WriteLine($"Unexpected error while checking process {pid}: {e.Message}"); } if (!hasExited) { stderr.WriteLine($"Killing process {pid} as it was cancelled"); kill(pid, 9); } }); if (timeout.HasValue) { if (!await WaitForExitAsync(process, timeout.Value)) { log.WriteLine($"Process {pid} didn't exit within {timeout} and will be killed"); await KillTreeAsync(pid, log, kill, getChildProcessIds, diagnostics ?? true); result.TimedOut = true; lock (stderr) { log.WriteLine($"{pid} Execution timed out after {timeout.Value.TotalSeconds} seconds and the process was killed."); } } } else { await WaitForExitAsync(process); } if (process.HasExited) { // make sure redirected output events are finished process.WaitForExit(); } Task.WaitAll(new Task[] { stderrCompletion.Task, stdoutCompletion.Task }, TimeSpan.FromSeconds(1)); try { result.ExitCode = process.ExitCode; log.WriteLine($"Process {Path.GetFileName(process.StartInfo.FileName)} exited with {result.ExitCode}"); } catch (Exception e) { result.ExitCode = 12345678; log.WriteLine($"Failed to get ExitCode: {e}"); } return result; } private static async Task WaitForExitAsync(Process process, TimeSpan? timeout = null) { if (process.HasExited) { return true; } var tcs = new TaskCompletionSource(); void ProcessExited(object? sender, EventArgs ea) { process.Exited -= ProcessExited; tcs.TrySetResult(true); } process.Exited += ProcessExited; process.EnableRaisingEvents = true; // Check if process exited again, in case it exited after we checked // the last time, but before we attached the event handler. if (process.HasExited) { process.Exited -= ProcessExited; tcs.TrySetResult(true); return true; } if (timeout.HasValue) { return await tcs.Task.TimeoutAfter(timeout.Value); } else { await tcs.Task; return true; } } private static Dictionary ToDictionary(IEnumerable enumerable) => enumerable.Cast().ToDictionary(v => v.Key.ToString()!, v => v.Value?.ToString(), StringComparer.Ordinal); #endregion } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Execution/ProcessManagerFactory.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Runtime.InteropServices; namespace Microsoft.DotNet.XHarness.Common.Execution; public static class ProcessManagerFactory { public static IProcessManager CreateProcessManager() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return new LinuxProcessManager(); } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return new MacOSProcessManager(); } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return new WindowsProcessManager(); } throw new InvalidOperationException("Unsupported OS platform detected when creating ProcessManager"); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Execution/UnixProcessManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.Common.Execution; public abstract class UnixProcessManager : ProcessManager { protected override List GetChildProcessIds(ILog log, int pid) => GetChildProcessIdsInternal(log, pid); protected static List GetChildProcessIdsInternal(ILog log, int pid) { var list = new List(); using (var ps = new Process()) { ps.StartInfo.FileName = "ps"; ps.StartInfo.Arguments = "-eo ppid,pid"; ps.StartInfo.UseShellExecute = false; ps.StartInfo.RedirectStandardOutput = true; ps.Start(); string stdout = ps.StandardOutput.ReadToEnd(); if (!ps.WaitForExit(1000)) { log.WriteLine("ps didn't finish in a reasonable amount of time (1 second)."); return list; } if (ps.ExitCode != 0) { return list; } stdout = stdout.Trim(); if (string.IsNullOrEmpty(stdout)) { return list; } var dict = new Dictionary>(); foreach (string line in stdout.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) { var l = line.Trim(); var space = l.IndexOf(' '); if (space <= 0) { continue; } var parent = l.Substring(0, space); var process = l.Substring(space + 1); if (int.TryParse(parent, out var parent_id) && int.TryParse(process, out var process_id)) { if (!dict.TryGetValue(parent_id, out var children)) { dict[parent_id] = children = new List(); } children.Add(process_id); } } var queue = new Queue(); queue.Enqueue(pid); do { var parent_id = queue.Dequeue(); list.Add(parent_id); if (dict.TryGetValue(parent_id, out var children)) { foreach (var child in children) { queue.Enqueue(child); } } } while (queue.Count > 0); } return list; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Execution/WindowsProcessManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Diagnostics; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.Common.Execution; public class WindowsProcessManager : ProcessManager { // We cannot enumerate processes well on Windows but we will use the CLI as .NET Core 3.1 and will kill the whole process tree // (this library is only used under netstandard 2.1 in Xamarin where it runs on OSX only) protected override List GetChildProcessIds(ILog log, int pid) => new() { pid }; protected override int Kill(int pid, int sig) { #if NET6_0_OR_GREATER Process.GetProcessById(pid).Kill(entireProcessTree: true); #else Process.GetProcessById(pid).Kill(); #endif return 0; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Logging/AggregatedLog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; namespace Microsoft.DotNet.XHarness.Common.Logging; public abstract partial class Log { public static IFileBackedLog CreateReadableAggregatedLog(IFileBackedLog defaultLog, params ILog[] logs) => new ReadableAggregatedLog(defaultLog, logs); public static ILog CreateAggregatedLog(params ILog[] logs) => new AggregatedLog(logs); // Log that will duplicate log output to multiple other logs. private class AggregatedLog : Log { protected readonly List _logs = new(); public AggregatedLog(params ILog[] logs) { _logs.AddRange(logs); Timestamp = false; } protected override void WriteImpl(string? value) { foreach (var log in _logs) { log.Write(value); } } public override void Write(byte[] buffer, int offset, int count) { foreach (var log in _logs) { log.Write(buffer, offset, count); } } public override void Flush() { foreach (var log in _logs) { log.Flush(); } } public override void Dispose() { foreach (var log in _logs) { log.Dispose(); } } } private class ReadableAggregatedLog : AggregatedLog, IFileBackedLog { private readonly IFileBackedLog _defaultLog; public ReadableAggregatedLog(IFileBackedLog defaultLog, params ILog[] logs) : base(logs) { _defaultLog = defaultLog ?? throw new ArgumentNullException(nameof(defaultLog)); // make sure that we also write in the default log _logs.Add(defaultLog); Timestamp = false; } public StreamReader GetReader() => _defaultLog.GetReader(); public string FullPath => _defaultLog.FullPath; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Logging/CallbackLog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Text; namespace Microsoft.DotNet.XHarness.Common.Logging; /// /// A log that forwards all written data to a callback /// public class CallbackLog : Log { private readonly Action _onWrite; private readonly StringBuilder _captured = new(); public CallbackLog(Action onWrite) : base("Callback log") { _onWrite = onWrite; Timestamp = false; } public override void Dispose() { GC.SuppressFinalize(this); } public override void Flush() { } protected override void WriteImpl(string? value) { if (value == null) { return; } lock (_captured) { _captured.Append(value); } _onWrite(value); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Logging/ConsoleLog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Text; namespace Microsoft.DotNet.XHarness.Common.Logging; /// /// A log that writes to standard output /// public class ConsoleLog : ReadableLog { readonly StringBuilder _captured = new(); protected override void WriteImpl(string? value) { lock (_captured) { _captured.Append(value); } Console.Write(value); } public override StreamReader GetReader() { lock (_captured) { var str = new MemoryStream(Encoding.GetBytes(_captured.ToString())); return new StreamReader(str, Encoding, false); } } public override void Flush() { } public override void Dispose() { GC.SuppressFinalize(this); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Logging/FileBackedLog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.Common.Logging; public interface IFileBackedLog : IReadableLog { string FullPath { get; } } public abstract class FileBackedLog : ReadableLog, IFileBackedLog { protected FileBackedLog(string? description = null) : base(description) { } public abstract string FullPath { get; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Logging/ILog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Text; namespace Microsoft.DotNet.XHarness.Common.Logging; public interface ILog : IDisposable { string? Description { get; set; } bool Timestamp { get; set; } Encoding Encoding { get; } void Write(byte[] buffer, int offset, int count); void Write(string? value); void WriteLine(string? value); void WriteLine(string format, params object?[] args); void Flush(); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Logging/Log.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Text; namespace Microsoft.DotNet.XHarness.Common.Logging; public abstract partial class Log : ILog { public virtual Encoding Encoding => Encoding.UTF8; public string? Description { get; set; } public virtual bool Timestamp { get; set; } = true; protected Log(string? description = null) { Description = description; } public virtual void Write(byte[] buffer, int offset, int count) => Write(Encoding.GetString(buffer, offset, count)); public void Write(string? value) { value ??= string.Empty; if (Timestamp) { value = "[" + DateTime.Now.ToString("HH:mm:ss.fffffff") + "] " + value; } WriteImpl(value); } public void WriteLine(string? value) => Write((value ?? string.Empty) + "\n"); public void WriteLine(string format, params object?[] args) => Write(string.Format(format, args) + "\n"); public override string ToString() => Description ?? string.Empty; public abstract void Flush(); public abstract void Dispose(); protected abstract void WriteImpl(string? value); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Logging/MemoryLog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Text; namespace Microsoft.DotNet.XHarness.Common.Logging; /// /// Log that only writes to memory /// public class MemoryLog : ReadableLog { private readonly StringBuilder _captured = new(); protected override void WriteImpl(string? value) => _captured.Append(value); public override StreamReader GetReader() { var str = new MemoryStream(Encoding.GetBytes(_captured.ToString())); return new StreamReader(str, Encoding, false); } public override void Flush() { } public override void Dispose() { GC.SuppressFinalize(this); } public override string ToString() => _captured.ToString(); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Logging/NullLog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Text; namespace Microsoft.DotNet.XHarness.Common.Logging; /// /// Log that discards everything /// public class NullLog : ILog { public string? Description { get; set; } = "NullLog"; public bool Timestamp { get; set; } public Encoding Encoding => Encoding.UTF8; public void Dispose() { GC.SuppressFinalize(this); } public void Flush() { } public void Write(byte[] buffer, int offset, int count) { } public void Write(string? value) { } public void WriteLine(string? value) { } public static void WriteLine(StringBuilder value) { } public void WriteLine(string format, params object?[] args) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Logging/ReadableLog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.IO; namespace Microsoft.DotNet.XHarness.Common.Logging; public interface IReadableLog : ILog { StreamReader GetReader(); } public abstract class ReadableLog : Log, IReadableLog { protected ReadableLog(string? description = null) : base(description) { } public abstract StreamReader GetReader(); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Logging/ScanLog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.Common.Logging; /// /// Log that scans for a given tag and notifies when tag is found in the stream. /// public class ScanLog : Log { private readonly string _tag; private readonly Action _tagFoundNotify; private readonly char[] _buffer; private int _startIndex; private bool _hasBeenFilled = false; public override bool Timestamp { get => false; set { } } public ScanLog(string tag, Action tagFoundNotify) { if (string.IsNullOrEmpty(tag)) { throw new ArgumentException($"'{nameof(tag)}' cannot be null or empty.", nameof(tag)); } _tag = tag; _tagFoundNotify = tagFoundNotify; _buffer = new char[_tag.Length]; _startIndex = -1; } protected override void WriteImpl(string? value) { if( value == null) { return; } foreach (var c in value) { Add(c); if (IsMatch()) { _tagFoundNotify(); } } } public override void Flush() { } public override void Dispose() { GC.SuppressFinalize(this); } private void Add(char c) { _startIndex++; if (_startIndex == _buffer.Length - 1) { _hasBeenFilled = true; } _startIndex %= _buffer.Length; _buffer[_startIndex] = c; } private bool IsMatch() { if (!_hasBeenFilled) { return false; } for (int i = 1; i <= _buffer.Length; i++) { int index = (i + _startIndex) % _buffer.Length; if (_buffer[index] != _tag[i - 1]) { return false; } } return true; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Microsoft.DotNet.XHarness.Common.csproj ================================================  $(XHarnessNetTFMs) true $(NoWarn);CS8002 ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/RunSummaryEmitter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.Common; /// /// Emits a structured JSON result block to the console log. /// Used by both Android and Apple platforms to provide consistent output for humans and automated tooling. /// public static class RunSummaryEmitter { public const string JsonStartMarker = "<>"; public const string JsonEndMarker = "<>"; /// /// Emits a structured JSON result block to the log. /// public static void EmitRunSummary( ILogger logger, ExitCode exitCode, string platform, string? deviceName, string? deviceOsVersion, string? architecture, int? instrumentationExitCode, IReadOnlyList producedFiles) { EmitRunSummary( message => logger.LogInformation(message), exitCode, platform, deviceName, deviceOsVersion, architecture, instrumentationExitCode, producedFiles); } /// /// Emits a structured JSON result block to the log. /// Uses an Action<string> for logging to support different logger abstractions. /// public static void EmitRunSummary( Action logInfo, ExitCode exitCode, string platform, string? deviceName, string? deviceOsVersion, string? architecture, int? instrumentationExitCode, IReadOnlyList producedFiles) { EmitJsonResultBlock(logInfo, exitCode, platform, deviceName, deviceOsVersion, architecture, instrumentationExitCode, producedFiles); } /// /// Emits a machine-readable JSON block between well-known delimiters for AI agents. /// When running in Helix, includes API URLs for direct file download. /// public static void EmitJsonResultBlock( Action logInfo, ExitCode exitCode, string platform, string? deviceName, string? deviceOsVersion, string? architecture, int? instrumentationExitCode, IReadOnlyList producedFiles) { var resultData = BuildResultData(exitCode, platform, deviceName, deviceOsVersion, architecture, instrumentationExitCode, producedFiles); var options = new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; string json = JsonSerializer.Serialize(resultData, options); logInfo($"{JsonStartMarker}{Environment.NewLine}{json}{Environment.NewLine}{JsonEndMarker}"); } /// /// Writes the JSON result block as a file (xharness-result.json) in the specified directory. /// This file gets uploaded to Helix automatically when written to the output/uploads directory. /// public static void WriteResultJsonFile( string outputDirectory, ExitCode exitCode, string platform, string? deviceName, string? deviceOsVersion, string? architecture, int? instrumentationExitCode, IReadOnlyList producedFiles) { try { var resultData = BuildResultData(exitCode, platform, deviceName, deviceOsVersion, architecture, instrumentationExitCode, producedFiles); var options = new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; string json = JsonSerializer.Serialize(resultData, options); Directory.CreateDirectory(outputDirectory); File.WriteAllText(Path.Combine(outputDirectory, "xharness-result.json"), json); } catch { // Best effort — don't fail the run if file writing fails } } private static Dictionary BuildResultData( ExitCode exitCode, string platform, string? deviceName, string? deviceOsVersion, string? architecture, int? instrumentationExitCode, IReadOnlyList producedFiles) { string? helixJobId = Environment.GetEnvironmentVariable("HELIX_CORRELATION_ID"); string? helixWorkItem = Environment.GetEnvironmentVariable("HELIX_WORKITEM_FRIENDLYNAME"); var fileEntries = new List(); foreach (var file in producedFiles) { var entry = new Dictionary { ["name"] = file.Name, ["type"] = file.Type, }; fileEntries.Add(entry); } var resultData = new Dictionary { ["version"] = 1, ["machineName"] = Environment.MachineName, ["exitCode"] = (int)exitCode, ["exitCodeName"] = exitCode.ToString(), ["platform"] = platform, }; if (!string.IsNullOrEmpty(helixJobId) && !string.IsNullOrEmpty(helixWorkItem)) { var encodedWorkItem = Uri.EscapeDataString(helixWorkItem); resultData["helixWorkItemId"] = helixWorkItem; resultData["helixJobId"] = helixJobId; resultData["helixConsoleUri"] = $"https://helix.dot.net/api/2019-06-17/jobs/{helixJobId}/workitems/{encodedWorkItem}/console"; resultData["helixFilesUri"] = $"https://helix.dot.net/api/2019-06-17/jobs/{helixJobId}/workitems/{encodedWorkItem}/files"; } if (instrumentationExitCode.HasValue) { resultData["instrumentationExitCode"] = instrumentationExitCode.Value; } if (!string.IsNullOrEmpty(deviceName)) { resultData["device"] = deviceName; } if (!string.IsNullOrEmpty(deviceOsVersion)) { resultData["deviceOsVersion"] = deviceOsVersion; } if (!string.IsNullOrEmpty(architecture)) { resultData["architecture"] = architecture; } if (fileEntries.Count > 0) { resultData["files"] = fileEntries; } return resultData; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/TargetPlatform.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.Common; public enum TargetPlatform { Android, Apple, WASM, WASI, } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Utilities/DisposableList.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; namespace Microsoft.DotNet.XHarness.Common.Utilities; public class DisposableList : List, IDisposable where T : IDisposable { public void Dispose() { foreach (var item in this) { item.Dispose(); } GC.SuppressFinalize(this); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Utilities/Extensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading.Tasks; namespace Microsoft.DotNet.XHarness.Common.Utilities; public static class Extensions { // Returns false if timed out public static async Task TimeoutAfter(this Task task, TimeSpan timeout) { if (timeout.Ticks < -1) { return false; } if (task == await Task.WhenAny(task, Task.Delay(timeout))) { return true; } else { return false; } } // Returns false if timed out public static async Task TimeoutAfter(this Task task, TimeSpan timeout) { if (timeout.Ticks < -1) { return false; } if (task == await Task.WhenAny(task, Task.Delay(timeout))) { return true; } else { return false; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Utilities/FileUtils.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; namespace Microsoft.DotNet.XHarness.Common.Utilities; public static class FileUtils { private static readonly string[] s_extensions = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new[] { ".exe", ".cmd", ".bat" } : new[] { "" }; public static bool TryFindExecutableInPATH(string filename, [NotNullWhen(true)] out string? fullPath, [NotNullWhen(false)] out string? errorMessage) { errorMessage = null; fullPath = null; if (File.Exists(filename)) { fullPath = Path.GetFullPath(filename); return true; } if (Path.IsPathRooted(filename)) { fullPath = filename; return true; } var path = Environment.GetEnvironmentVariable("PATH"); if (string.IsNullOrEmpty(path)) { errorMessage = "Could not find environment variable PATH"; return false; } string[] searchPaths = path.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); if (searchPaths.Length == 0) { errorMessage = $"No paths set in environment variable PATH"; return false; } List filenamesTried = new(s_extensions.Length); foreach (string extn in s_extensions) { string filenameWithExtn = filename + extn; filenamesTried.Add(filenameWithExtn); foreach (string searchPath in searchPaths) { var pathToCheck = Path.Combine(searchPath, filenameWithExtn); if (File.Exists(pathToCheck)) { fullPath = pathToCheck; return true; } } } // Could not find the path errorMessage = $"Tried to look for {string.Join(", ", filenamesTried)} in PATH: {string.Join(", ", searchPaths)} ."; return false; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/Utilities/StringUtils.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Microsoft.DotNet.XHarness.Common.Utilities; public class StringUtils { private static readonly char s_shellQuoteChar; private static readonly char[] s_mustQuoteCharacters = { ' ', '\'', ',', '$', '\\' }; private static readonly char[] s_mustQuoteCharactersProcess = { ' ', '\\', '"', '\'' }; static StringUtils() { PlatformID pid = Environment.OSVersion.Platform; if ((int)pid != 128 && pid != PlatformID.Unix && pid != PlatformID.MacOSX) { s_shellQuoteChar = '"'; // Windows } else { s_shellQuoteChar = '\''; // !Windows } } public static string FormatArguments(params string[] arguments) => FormatArguments((IList)arguments); public static string FormatArguments(IList arguments) => string.Join(" ", QuoteForProcess(arguments) ?? Array.Empty()); private static string[]? QuoteForProcess(params string[] array) { if (array == null || array.Length == 0) { return array; } var rv = new string[array.Length]; for (var i = 0; i < array.Length; i++) { rv[i] = QuoteForProcess(array[i]); } return rv; } public static string Quote(string? f) { if (string.IsNullOrEmpty(f)) { return f ?? string.Empty; } if (f.IndexOfAny(s_mustQuoteCharacters) == -1) { return f; } var s = new StringBuilder(); s.Append(s_shellQuoteChar); foreach (var c in f) { if (c == '\'' || c == '"' || c == '\\') { s.Append('\\'); } s.Append(c); } s.Append(s_shellQuoteChar); return s.ToString(); } // Quote input according to how System.Diagnostics.Process needs it quoted. private static string QuoteForProcess(string f) { if (string.IsNullOrEmpty(f)) { return f ?? string.Empty; } if (f.IndexOfAny(s_mustQuoteCharactersProcess) == -1) { return f; } var s = new StringBuilder(); s.Append('"'); foreach (var c in f) { if (c == '"') { s.Append('\\'); s.Append(c).Append(c); } else if (c == '\\') { s.Append(c); } s.Append(c); } s.Append('"'); return s.ToString(); } private static string[]? QuoteForProcess(IList arguments) { if (arguments == null) { return Array.Empty(); } return QuoteForProcess(arguments.ToArray()); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/WasmSymbolicatorBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.Common; public abstract class WasmSymbolicatorBase { public string? SymbolsFile { get; private set; } public string? SymbolsPatternFile { get; private set; } public virtual bool Init(string? symbolMapFile, string? symbolsPatternFile, ILogger logger) { SymbolsFile = symbolMapFile; SymbolsPatternFile = symbolsPatternFile; return true; } public static WasmSymbolicatorBase? Create(Type? symbolicatorType, string? symbolMapFile, string? symbolsPatternFile, ILogger logger) { if (symbolicatorType is null && symbolMapFile is null && symbolsPatternFile is null) return null; if (symbolicatorType is null) { logger.LogWarning("No symbolicator given"); return null; } var symbolicator = Activator.CreateInstance(symbolicatorType) as WasmSymbolicatorBase; if (symbolicator is null) { // should not happen logger.LogError($"Symbolicator '{symbolicatorType}' is not of WasmSymbolicatorBase type."); return null; } if (symbolicator.Init(symbolMapFile, symbolsPatternFile, logger) == true) return symbolicator; logger.LogDebug($"Symbolicator '{symbolicatorType}'.Init({symbolMapFile}, {symbolsPatternFile}) returned false"); return null; } public abstract string Symbolicate(string msg); } ================================================ FILE: src/Microsoft.DotNet.XHarness.Common/XmlResultJargon.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.Common; public enum XmlResultJargon { TouchUnit, TouchUnit_NUnitV2 = TouchUnit, TouchUnit_NUnitV3, NUnitV2, NUnitV3, xUnit, Trx, Missing, } ================================================ FILE: src/Microsoft.DotNet.XHarness.InstrumentationBase.Xunit/DefaultAndroidEntryPoint.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Reflection; using Microsoft.DotNet.XHarness.TestRunners.Common; using Microsoft.DotNet.XHarness.TestRunners.Xunit; namespace Microsoft.DotNet.XHarness.DefaultAndroidEntryPoint.Xunit; /// /// Class that implements basic functionality for the entry point of /// the Android test application. /// /// The minimum required to run succesfully the test app based on /// DefaultAndroidEntryPoint is to provide results-file-path as an argument. /// /// Client is able to provide test assemblies by overriding /// GetTestAssemblies method. /// /// Other methods such as Device, Logger etc. have default implementation /// but can be overrided if needed. /// /// public class DefaultAndroidEntryPoint : AndroidApplicationEntryPoint { private readonly string _resultsPath; private readonly string? _excludeCategoriesDir; private readonly string? _excludeCategoriesFile; private readonly Dictionary _parsedArguments; public const string ResultsFileArgumentName = "results-file-name"; public const string ResultsFileArgumentPath = "results-file-path"; public const string ExcludeCategoriesDirArgumentName = "exclude-categories-dir"; public const string ExcludeCategoriesFileArgumentName = "exclude-categories-file"; public const string ExcludeMethodArgumentName = "exclude-method"; public const string IncludeMethodArgumentName = "include-method"; public const string ExcludeClassArgumentName = "exclude-class"; public const string IncludeClassArgumentName = "include-class"; protected override string? IgnoreFilesDirectory => _excludeCategoriesDir; protected override string? IgnoredTraitsFilePath => _excludeCategoriesFile; public DefaultAndroidEntryPoint(string resultsPath, Dictionary optionalBundle) { _parsedArguments = optionalBundle; // use default name for test results file _parsedArguments.TryAdd(ResultsFileArgumentName, "TestResults.xml"); if (!Directory.Exists(resultsPath)) { Directory.CreateDirectory(resultsPath); } _resultsPath = Path.Combine(resultsPath, _parsedArguments[ResultsFileArgumentName]); _excludeCategoriesDir = _parsedArguments.GetValueOrDefault(ExcludeCategoriesDirArgumentName); _excludeCategoriesFile = _parsedArguments.GetValueOrDefault(ExcludeCategoriesFileArgumentName); } protected override bool LogExcludedTests => true; public override TextWriter? Logger => null; public override string TestsResultsFinalPath => _resultsPath; protected override int? MaxParallelThreads => System.Environment.ProcessorCount / 2; protected override IDevice? Device => null; public IEnumerable Tests { get; set; } = new List(); protected override IEnumerable GetTestAssemblies() { foreach (var assembly in Tests) { yield return new TestAssemblyInfo(assembly, assembly.Location); } } protected override void TerminateWithSuccess() { } private static void ConfigureFilters(string? filter, Action filterMethod, bool isExcluded) { if (filter != null) { foreach (var f in filter.Split(' ')) { filterMethod(f, isExcluded); } } } protected override TestRunner GetTestRunner(LogWriter logWriter) { var testRunner = base.GetTestRunner(logWriter); (string? Filter, Action FilterMethod, bool IsExcluded)[] filters = { (_parsedArguments.GetValueOrDefault(ExcludeMethodArgumentName), testRunner.SkipMethod, true), (_parsedArguments.GetValueOrDefault(ExcludeClassArgumentName), testRunner.SkipClass, true), (_parsedArguments.GetValueOrDefault(IncludeMethodArgumentName), testRunner.SkipMethod, false), (_parsedArguments.GetValueOrDefault(IncludeClassArgumentName), testRunner.SkipClass, false) }; foreach (var t in filters) { ConfigureFilters(t.Filter, t.FilterMethod, t.IsExcluded); } return testRunner; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.InstrumentationBase.Xunit/Microsoft.DotNet.XHarness.DefaultAndroidEntryPoint.Xunit.csproj ================================================  $(XHarnessNetTFMs) true Microsoft.DotNet.XHarness.TestRunners.Xunit ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/AndroidApplicationEntryPointBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Threading.Tasks; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Common; /// /// Implementors should provide a text writter than will be used to /// write the logging of the tests that are executed. /// public abstract class AndroidApplicationEntryPointBase : ApplicationEntryPoint { public abstract TextWriter? Logger { get; } /// /// Implementors should provide a full path in which the final /// results of the test run will be written. This property must not /// return null. /// public abstract string TestsResultsFinalPath { get; } public override async Task RunAsync() { var options = ApplicationOptions.Current; using TextWriter? resultsFileMaybe = options.EnableXml ? File.CreateText(TestsResultsFinalPath) : null; await InternalRunAsync(options, Logger, resultsFileMaybe); if (CoverageResultPath != null) { // Report the coverage path via stdout so the Instrumentation class can // forward it as INSTRUMENTATION_RESULT: coverage-results-path= Console.WriteLine($"INSTRUMENTATION_RESULT: coverage-results-path={CoverageResultPath}"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/ApplicationEntryPoint.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Common; /// /// States the type of runner to be used by the application. /// public enum TestRunnerType { NUnit, Xunit, } /// /// Abstract class that represents the entry point of the test application. /// /// Subclasses must provide the minimum implementation to ensure that: /// /// Device: We do have the required device information for the logger. /// Assemblies: Provide a list of the assembly information to run. /// assemblies can be loaded from disk or from memory, this is up to the /// implementor. /// /// Clients that register to the class events and want to update the UI /// are responsible to do so in the main UI thread. The application entry /// point does not guarantee that the tests are executed in the ui thread. /// /// public abstract class ApplicationEntryPoint { /// /// Event raised when the test run has started. /// public event EventHandler? TestsStarted; /// /// Event raised when the test run has completed. /// public event EventHandler? TestsCompleted; // fwd the events from the runner so that clients can connect to them /// /// Event raised when a test has started. /// public event EventHandler? TestStarted; /// /// Event raised when a test has completed or has been skipped. /// public event EventHandler<(string TestName, TestResult TestResult)>? TestCompleted; protected abstract int? MaxParallelThreads { get; } /// /// Must be implemented and return a class that returns the information /// of a device. It can return null. /// protected abstract IDevice? Device { get; } /// /// Returns the IEnumerable with the asseblies that contain the tests /// to be ran. /// /// protected abstract IEnumerable GetTestAssemblies(); /// /// Returns the runner to be used. /// protected abstract TestRunner GetTestRunner(LogWriter logWriter); protected abstract bool IsXunit { get; } /// /// Returns the directory that contains the ignore files. In order to ignore certain traits in the /// runner the directory most contain one of the two following files: /// /// * xunit-excludes.txt: Contains the traits to be ignored in xunit. /// * nunit-excludes.txt: Contains the categories to be ignored in nunit. /// /// The default implementation returns null and therefore no traits/categories are ignored. /// /// If the directory contains any *.ignore files, those will be parse to ignore specific tests that /// are known to fail. The format of the file is as follows: /// /// * A test name per line /// * lines that start with # will be ignored and can be used as comments. /// * the 'KLASS:' prefix can be used to ignore all the tests in a class. /// * the 'Platform32:' prefix can be used to ignore a test but only in a 32b arch device. /// protected virtual string? IgnoreFilesDirectory => null; /// /// Returns the path to a file that contains the list of traits to ignore in the following format: /// traitname=traitvalue /// /// The default implementation will return null and therefore no traits will be ignored. /// protected virtual string? IgnoredTraitsFilePath => null; /// /// States if the skipped tests should be logged. Helpful to determine why some tests are executed and others /// are not. /// protected virtual bool LogExcludedTests { get; } = false; /// /// Terminates the application. This should ensure that it is executed /// in the main thread. /// protected abstract void TerminateWithSuccess(); /// /// Execute the tests in an async mode. /// public abstract Task RunAsync(); /// /// Get/Set the minimun log level to be used by the runner logging. /// public MinimumLogLevel MinimumLogLevel { get; set; } = MinimumLogLevel.Info; private void OnTestStarted(object? sender, string testName) => TestStarted?.Invoke(sender, testName); private void OnTestCompleted(object? sender, (string TestName, TestResult Testresult) result) => TestCompleted?.Invoke(sender, result); private async Task> GetIgnoredCategories() { var categories = new List(); // default known category to ignore // check if the child does have an ignore files dir if (!string.IsNullOrEmpty(IgnoreFilesDirectory)) { categories.AddRange(await IgnoreFileParser.ParseTraitsContentFileAsync(IgnoreFilesDirectory, IsXunit)); } // check if the child provides a specific traits file. if (!string.IsNullOrEmpty((IgnoredTraitsFilePath))) { categories.AddRange(await IgnoreFileParser.ParseTraitsFileAsync(IgnoredTraitsFilePath)); } return categories; } internal static void ConfigureRunnerFilters(TestRunner runner, ApplicationOptions options) { runner.RunAllTestsByDefault = options.RunAllTestsByDefault; // Add the provided method and class filters if (options.SingleMethodFilters.Count != 0 || options.ClassMethodFilters.Count != 0) { // Having methods/classes explicitly specified means only those methods/classes should be run runner.RunAllTestsByDefault = false; foreach (string methodName in options.SingleMethodFilters) { runner.SkipMethod(methodName, isExcluded: false); } foreach (string className in options.ClassMethodFilters) { runner.SkipClass(className, isExcluded: false); } } } private static async Task WriteResults(TestRunner runner, ApplicationOptions options, LogWriter logger, TextWriter writer) { if (options.EnableXml && writer == null) { throw new ArgumentNullException(nameof(writer)); } if (options.EnableXml) { await runner.WriteResultsToFile(writer, options.XmlVersion); logger.Info("Xml file was written to the provided writer."); } else { string resultsFilePath = await runner.WriteResultsToFile(options.XmlVersion); logger.Info($"XML results can be found in '{resultsFilePath}'"); } } /// /// Path to the coverage results file after test execution, if coverage was enabled and collected. /// public string? CoverageResultPath { get; private set; } private async Task InternalRunAsync(LogWriter logger) { logger.MinimumLogLevel = MinimumLogLevel; var runner = GetTestRunner(logger); runner.LogExcludedTests = LogExcludedTests; // connect to the runner events so that we fwd them to the client runner.TestStarted += OnTestStarted; runner.TestCompleted += OnTestCompleted; // add ignored categories and specific files runner.SkipCategories(await GetIgnoredCategories()); runner.SkipTests(await IgnoreFileParser.ParseContentFilesAsync(IgnoreFilesDirectory)); var testAssemblies = GetTestAssemblies(); // Set up coverage before running tests var options = ApplicationOptions.Current; CoverageManager? coverageManager = null; if (options.EnableCoverage) { coverageManager = new CoverageManager(options.CoverageOutputPath); coverageManager.PrepareForCoverage(); logger.Info("Code coverage enabled. Coverage output path: " + coverageManager.OutputPath); } // notify the clients we are starting TestsStarted?.Invoke(this, new EventArgs()); await runner.Run(testAssemblies).ConfigureAwait(false); // Check for coverage results if enabled (regardless of test outcome) if (coverageManager != null) { CoverageResultPath = coverageManager.GetCoverageResults(); if (CoverageResultPath != null) { logger.Info($"Coverage results found at: {CoverageResultPath}"); } else { logger.Info("Coverage was enabled but no coverage results file was found."); } } var result = new TestRunResult(runner); // notify the client we are done and the results, but do not expose // the runner. TestsCompleted?.Invoke(this, result); return runner; } protected async Task InternalRunAsync(ApplicationOptions options, TextWriter? loggerWriter, TextWriter? resultsFile) { // we generate the logs in two different ways depending if the generate xml flag was // provided. If it was, we will write the xml file to the provided writer if present, else // we will write the normal console output using the LogWriter var logger = (loggerWriter == null || options.EnableXml) ? new LogWriter(Device) : new LogWriter(Device, loggerWriter); logger.MinimumLogLevel = MinimumLogLevel.Info; var runner = await InternalRunAsync(logger); await WriteResults(runner, options, logger, resultsFile ?? Console.Out); logger.Info($"{Environment.NewLine}=== TEST EXECUTION SUMMARY ==={Environment.NewLine}Tests run: {runner.TotalTests} Passed: {runner.PassedTests} Inconclusive: {runner.InconclusiveTests} Failed: {runner.FailedTests} Ignored: {runner.FilteredTests} Skipped: {runner.SkippedTests}{Environment.NewLine}"); if (options.AppEndTag != null) { logger.Info(options.AppEndTag); } if (options.TerminateAfterExecution) { TerminateWithSuccess(); } return runner; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/ApplicationOptions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Mono.Options; namespace Microsoft.DotNet.XHarness.TestRunners.Common; internal enum XmlMode { Default = 0, Wrapped = 1, } public class ApplicationOptions { public static ApplicationOptions Current = new(); private readonly List _singleMethodFilters = new(); private readonly List _classMethodFilters = new(); public ApplicationOptions() { if (bool.TryParse(Environment.GetEnvironmentVariable(EnviromentVariables.AutoExit), out bool b)) { TerminateAfterExecution = b; } if (bool.TryParse(Environment.GetEnvironmentVariable(EnviromentVariables.AutoStart), out b)) { AutoStart = b; } if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnviromentVariables.HostName))) { HostName = Environment.GetEnvironmentVariable(EnviromentVariables.HostName); } if (int.TryParse(Environment.GetEnvironmentVariable(EnviromentVariables.HostPort), out int i)) { HostPort = i; } if (bool.TryParse(Environment.GetEnvironmentVariable(EnviromentVariables.EnableXmlOutput), out b)) { EnableXml = b; } if (bool.TryParse(Environment.GetEnvironmentVariable(EnviromentVariables.UseTcpTunnel), out b)) { UseTunnel = b; } var xml_version = Environment.GetEnvironmentVariable(EnviromentVariables.XmlVersion); if (!string.IsNullOrEmpty(xml_version)) { XmlVersion = (XmlResultJargon)Enum.Parse(typeof(XmlResultJargon), xml_version, true); } if (bool.TryParse(Environment.GetEnvironmentVariable(EnviromentVariables.RunAllTestsByDefault), out b)) { RunAllTestsByDefault = b; } if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnviromentVariables.SkippedMethods))) { var methods = Environment.GetEnvironmentVariable(EnviromentVariables.SkippedMethods); _singleMethodFilters.AddRange(methods.Split(',')); } if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnviromentVariables.SkippedClasses))) { var classes = Environment.GetEnvironmentVariable(EnviromentVariables.SkippedClasses); _classMethodFilters.AddRange(classes.Split(',')); } if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnviromentVariables.AppEndTag))) { AppEndTag = Environment.GetEnvironmentVariable(EnviromentVariables.AppEndTag); } if (bool.TryParse(Environment.GetEnvironmentVariable(EnviromentVariables.EnableCoverage), out b)) { EnableCoverage = b; } if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnviromentVariables.CoverageOutputPath))) { CoverageOutputPath = Environment.GetEnvironmentVariable(EnviromentVariables.CoverageOutputPath); } var os = new OptionSet() { { "autoexit", "Exit application once the test run has completed", v => TerminateAfterExecution = true }, { "autostart", "If the app should automatically start running the tests", v => AutoStart = true }, { "hostname=", "Comma-separated list of host names or IP address to (try to) connect to", v => HostName = v }, { "hostport=", "HTTP/TCP port to connect to", v => HostPort = int.Parse (v) }, { "tcp-tunnel", "Use a TCP tunnel for communication between the app and XHarness", v => UseTunnel = true }, { "enablexml", "Enable the xml reported", v => EnableXml = false }, { "xmlversion", "The XML format", v => XmlVersion = (XmlResultJargon) Enum.Parse (typeof (XmlResultJargon), v, false) }, { "run-all-tests:", "Run all the tests found in the assembly, defaults to true", v => { // if cannot parse, use default if (bool.TryParse(v, out var runAll)) { RunAllTestsByDefault = runAll; } } }, { "method|m=", "Method to be ran in the test application. When this parameter is used only the " + "tests that have been provided by the '--method' and '--class' arguments will be ran. " + "All other test will be ignored. Can be used more than once.", v => _singleMethodFilters.Add(v) }, { "class|c=", "Method to be ran in the test application. When this parameter is used only the " + "tests that have been provided by the '--method' and '--class' arguments will be ran. " + "All other test will be ignored. Can be used more than once.", v => _classMethodFilters.Add(v) }, { "test-end-tag=", "String that will be outputted when test run has finished", v => AppEndTag = v }, { "enable-coverage", "Enable code coverage collection", v => EnableCoverage = true }, { "coverage-output=", "Path for coverage output file", v => CoverageOutputPath = v }, }; try { os.Parse(Environment.GetCommandLineArgs()); } catch (OptionException oe) { Console.WriteLine("{0} for options '{1}'", oe.Message, oe.OptionName); } } /// /// Specify if tests should start without human input. /// public bool AutoStart { get; set; } /// /// Specify the version of Xml to be used for the results. /// public XmlResultJargon XmlVersion { get; private set; } = XmlResultJargon.xUnit; /// /// Return the test results as xml. /// public bool EnableXml { get; private set; } = true; // always true by default /// /// Use a TCP tunnel for communication between the app and XHarness. /// public bool UseTunnel { get; private set; } /// /// The name of the host that has the device plugged. /// public string HostName { get; private set; } /// /// The port of the host that has the device plugged. /// public int HostPort { get; private set; } /// /// Specify is the application should exit once the tests are completed. /// public bool TerminateAfterExecution { get; private set; } /// /// Specify if all the tests should be run by default or not. Defaults to true. /// public bool RunAllTestsByDefault { get; private set; } = true; /// /// Specify the methods to be ran in the app. /// public ICollection SingleMethodFilters => _singleMethodFilters; /// /// Specify the test classes to be ran in the app. /// public ICollection ClassMethodFilters => _classMethodFilters; /// /// String that will be outputted when test run has finished. /// public string AppEndTag { get; private set; } /// /// Enable code coverage collection during test execution. /// public bool EnableCoverage { get; private set; } /// /// Output path for coverage results file. /// public string CoverageOutputPath { get; private set; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/CoverageManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Common; /// /// Manages code coverage collection for XHarness device tests. /// /// Looks for a coverage file produced by an external tool (e.g. coverlet) at the /// configured output path or in the same directory. The file is then transported /// back to the host by the platform-specific plumbing (adb pull, app container, etc.). /// public class CoverageManager { public string OutputPath { get; } public CoverageManager(string? outputPath) { if (string.IsNullOrEmpty(outputPath)) { // Default: same directory as iOS test results (Documents on iOS, temp elsewhere) var personalDir = Environment.GetFolderPath(Environment.SpecialFolder.Personal); OutputPath = !string.IsNullOrEmpty(personalDir) ? Path.Combine(personalDir, "coverage.cobertura.xml") : Path.Combine(Path.GetTempPath(), "coverage.cobertura.xml"); } else if (!Path.IsPathRooted(outputPath)) { // Resolve relative paths against the Personal/Documents directory on mobile var personalDir = Environment.GetFolderPath(Environment.SpecialFolder.Personal); OutputPath = !string.IsNullOrEmpty(personalDir) ? Path.Combine(personalDir, outputPath) : Path.Combine(Environment.CurrentDirectory, outputPath); } else { OutputPath = outputPath; } } /// /// Prepares for coverage collection. Call before test execution. /// Creates the output directory and sets the COVERAGE_OUTPUT_PATH environment variable /// so that on-device coverage tools know where to write their results. /// public void PrepareForCoverage() { var outputDir = Path.GetDirectoryName(OutputPath); if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir)) { Directory.CreateDirectory(outputDir); } Environment.SetEnvironmentVariable("COVERAGE_OUTPUT_PATH", OutputPath); } /// /// Gets coverage results. Returns the path to the coverage file if an external tool /// has already written one, or null if no file was found. /// public string? GetCoverageResults() { if (File.Exists(OutputPath)) { return OutputPath; } var directory = Path.GetDirectoryName(OutputPath); if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory)) { foreach (var file in Directory.GetFiles(directory, "coverage.cobertura.xml")) { return file; } } return null; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/Extensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.TestRunners.Common; internal static partial class Extensions { public static string YesNo(this bool b) => b ? "yes" : "no"; } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/IDevice.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.TestRunners.Common; /// /// Interface to be implemented by those classes that provide the required /// information of the device that is being used so that we can add the /// device information in the test logs. /// public interface IDevice { string BundleIdentifier { get; } string UniqueIdentifier { get; } string Name { get; } string Model { get; } string SystemName { get; } string SystemVersion { get; } string Locale { get; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/IgnoreFileParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading.Tasks; namespace Microsoft.DotNet.XHarness.TestRunners.Common; /// /// Class that can parse a file/stream with the ignored tests and will /// return a list of the ignored tests. /// internal static class IgnoreFileParser { private static string ParseLine(string line) { // we have to make sure of several things, first, lets // remove any char after the first # which would mean // we have comments: var pos = line.IndexOf('#'); if (pos > -1) { line = line.Remove(pos); } line = line.Trim(); return line; } public static async Task> ParseStreamAsync(TextReader textReader) { var ignoredMethods = new List(); string line; while ((line = await textReader.ReadLineAsync()) != null) { line = ParseLine(line); if (string.IsNullOrEmpty(line)) { continue; } ignoredMethods.Add(line); } return ignoredMethods; } public static async Task> ParseAssemblyResourcesAsync(Assembly asm) { var ignoredTests = new List(); // the project generator added the required resources, // we extract them, parse them and add the result foreach (var resourceName in asm.GetManifestResourceNames()) { if (resourceName.EndsWith(".ignore", StringComparison.Ordinal)) { using (var stream = asm.GetManifestResourceStream(resourceName)) using (var reader = new StreamReader(stream)) { var ignored = await ParseStreamAsync(reader); // we could have more than one file, lets add them ignoredTests.AddRange(ignored); } } } return ignoredTests; } public static async Task> ParseContentFilesAsync(string contentDir) { if (string.IsNullOrEmpty(contentDir)) { return Array.Empty(); } var ignoredTests = new List(); foreach (var f in Directory.GetFiles(contentDir, "*.ignore")) { using (var reader = new StreamReader(f)) { var ignored = await ParseStreamAsync(reader); ignoredTests.AddRange(ignored); } } return ignoredTests; } public static async Task> ParseTraitsFileAsync(string filePath) { var ignoredTraits = new List(); using var reader = new StreamReader(filePath); string line; while ((line = await reader.ReadLineAsync()) != null) { if (string.IsNullOrEmpty(line)) { continue; } ignoredTraits.Add(line); } return ignoredTraits; } public static Task> ParseTraitsContentFileAsync(string contentDir, bool isXUnit) { var ignoreFile = Path.Combine(contentDir, isXUnit ? "xunit-excludes.txt" : "nunit-excludes.txt"); return ParseTraitsFileAsync(ignoreFile); } public static IEnumerable ParseTraitsContentFile(string contentDir, bool isXUnit) { var ignoredTraits = new List(); var ignoreFile = Path.Combine(contentDir, isXUnit ? "xunit-excludes.txt" : "nunit-excludes.txt"); using (var reader = new StreamReader(ignoreFile)) { string line; while ((line = reader.ReadLine()) != null) { if (string.IsNullOrEmpty(line)) { continue; } ignoredTraits.Add(line); } } return ignoredTraits; } public static IEnumerable ParseContentFiles(string contentDir) { var ignoredTests = new List(); foreach (var f in Directory.GetFiles(contentDir, "*.ignore")) { using (var reader = new StreamReader(f)) { string line; while ((line = reader.ReadLine()) != null) { line = ParseLine(line); if (string.IsNullOrEmpty(line)) { continue; } ignoredTests.Add(line); } } } return ignoredTests; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/LogWriter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.IO; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Common; public class LogWriter { private readonly TextWriter _writer; private readonly IDevice? _device; public MinimumLogLevel MinimumLogLevel { get; set; } = MinimumLogLevel.Info; public LogWriter() : this(null, Console.Out) { } public LogWriter(IDevice? device) : this(device, Console.Out) { } public LogWriter(TextWriter w) : this(null, w) { } public LogWriter(IDevice? device, TextWriter writer) { _writer = writer ?? Console.Out; _device = device; if (_device is not null) // we just write the header if we do have the device info { InitLogging(); } } [System.Runtime.InteropServices.DllImport("/usr/lib/libobjc.dylib")] private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector); public void InitLogging() { Debug.Assert(_device is not null); // print some useful info _writer.WriteLine("[Runner executing:\t{0}]", "Run everything"); _writer.WriteLine("[{0}:\t{1} v{2}]", _device.Model, _device.SystemName, _device.SystemVersion); _writer.WriteLine("[Device Name:\t{0}]", _device.Name); _writer.WriteLine("[Device UDID:\t{0}]", _device.UniqueIdentifier); _writer.WriteLine("[Device Locale:\t{0}]", _device.Locale); _writer.WriteLine("[Device Date/Time:\t{0}]", DateTime.Now); // to match earlier C.WL output _writer.WriteLine("[Bundle:\t{0}]", _device.BundleIdentifier); } public void OnError(string message) { if (MinimumLogLevel < MinimumLogLevel.Error) { return; } _writer.WriteLine(message); _writer.Flush(); } public void OnWarning(string message) { if (MinimumLogLevel < MinimumLogLevel.Warning) { return; } _writer.WriteLine(message); _writer.Flush(); } public void OnDebug(string message) { if (MinimumLogLevel < MinimumLogLevel.Debug) { return; } _writer.WriteLine(message); _writer.Flush(); } public void OnDiagnostic(string message) { if (MinimumLogLevel < MinimumLogLevel.Verbose) { return; } _writer.WriteLine(message); _writer.Flush(); } public void OnInfo(string message) { if (MinimumLogLevel < MinimumLogLevel.Info) { return; } _writer.WriteLine(message); _writer.Flush(); } public void Info(string message) { _writer.WriteLine(message); _writer.Flush(); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/Microsoft.DotNet.XHarness.TestRunners.Common.csproj ================================================  $(XHarnessNetTFMs) true $(NoWarn);CS8002 disable ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/MinimumLogLevel.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.TestRunners.Common; public enum MinimumLogLevel { Critical, Error, Warning, Info, Debug, Verbose } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/Properties/AssemblyInfo.cs ================================================ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.DotNet.XHarness.TestRunners.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.DotNet.XHarness.TestRunners.Xunit, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.DotNet.XHarness.TestRunners.NUnit, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/TcpTextWriter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Common; /// /// Class that writes output into a TCP connection. /// It is an adaptation of NUnitLite's TcpWriter.cs with additional overrides and with network-activity UI enhancement /// This code is a small modification of https://github.com/spouliot/Touch.Unit/blob/master/NUnitLite/TouchRunner/TcpTextWriter.cs /// internal class TcpTextWriter : TextWriter { private static readonly TimeSpan s_connectionAwaitPeriod = TimeSpan.FromMinutes(1); private readonly StreamWriter _writer; private TcpTextWriter(StreamWriter writer) { _writer = writer ?? throw new ArgumentNullException(nameof(writer)); } public static TcpTextWriter InitializeWithTunnelConnection(int port) { ValidatePort(port); var server = new TcpListener(IPAddress.Any, port); server.Server.ReceiveTimeout = 5000; server.Start(); var watch = Stopwatch.StartNew(); while (!server.Pending()) { if (watch.Elapsed > s_connectionAwaitPeriod) { throw new Exception($"No inbound TCP connection after {(int)s_connectionAwaitPeriod.TotalSeconds} seconds"); } Thread.Sleep(100); } var client = server.AcceptTcpClient(); // Block until we have the ping from the client side byte[] buffer = new byte[16 * 1024]; var stream = client.GetStream(); while ((_ = stream.Read(buffer, 0, buffer.Length)) != 0) { var message = Encoding.UTF8.GetString(buffer); if (message.Contains("ping")) { break; } } var writer = new StreamWriter(client.GetStream()); return new TcpTextWriter(writer); } public static TcpTextWriter InitializeWithDirectConnection(string hostName, int port) { if (hostName is null) { throw new ArgumentNullException(nameof(hostName)); } ValidatePort(port); hostName = SelectHostName(hostName.Split(','), port); var client = new TcpClient(hostName, port); var writer = new StreamWriter(client.GetStream()); return new TcpTextWriter(writer); } // we override everything that StreamWriter overrides from TextWriter public override Encoding Encoding => Encoding.UTF8; public override void Close() { _writer.Close(); } protected override void Dispose(bool disposing) => _writer?.Dispose(); public override void Flush() { _writer.Flush(); } // minimum to override - see http://msdn.microsoft.com/en-us/library/system.io.textwriter.aspx public override void Write(char value) { _writer.Write(value); } public override void Write(char[]? buffer) { _writer.Write(buffer); } public override void Write(char[] buffer, int index, int count) { _writer.Write(buffer, index, count); } public override void Write(string? value) { _writer.Write(value); } // special extra override to ensure we flush data regularly public override void WriteLine() { _writer.WriteLine(); _writer.Flush(); } private static string SelectHostName(string[] names, int port) { if (names.Length == 1) { return names[0]; } object lock_obj = new object(); string? result = null; int failures = 0; using (var evt = new ManualResetEvent(false)) { for (int i = names.Length - 1; i >= 0; i--) { var name = names[i]; ThreadPool.QueueUserWorkItem((v) => { try { var client = new TcpClient(name, port); using (var writer = new StreamWriter(client.GetStream())) { writer.WriteLine("ping"); } lock (lock_obj) { if (result == null) { result = name; } } evt.Set(); } catch (Exception) { lock (lock_obj) { failures++; if (failures == names.Length) { evt.Set(); } } } }); } // Wait for 1 success or all failures evt.WaitOne(); } if (result == null) { throw new InvalidOperationException("Couldn't connect to any of the hostnames."); } return result; } private static void ValidatePort(int port) { if (port < 0 || port > ushort.MaxValue) { throw new ArgumentOutOfRangeException(nameof(port), $"Port must be between 0 and {ushort.MaxValue}"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/TestAssemblyInfo.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Reflection; namespace Microsoft.DotNet.XHarness.TestRunners.Common; public class TestAssemblyInfo { public Assembly Assembly { get; } public string FullPath { get; } public TestAssemblyInfo(Assembly assembly, string fullPath) { Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); FullPath = fullPath ?? string.Empty; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/TestCompletionStatus.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.TestRunners.Common; internal enum TestCompletionStatus { Undefined, Passed, Failed, Skipped, Inconclusive, } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/TestExecutionState.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.TestRunners.Common; internal class TestExecutionState { public string TestName { get; internal set; } public TimeSpan Started { get; private set; } = TimeSpan.MinValue; public TimeSpan Finished { get; private set; } = TimeSpan.MinValue; public TestCompletionStatus CompletionStatus { get; set; } = TestCompletionStatus.Undefined; internal TestExecutionState() { } internal void Start() => Started = new TimeSpan(DateTime.Now.Ticks); internal void Finish() => Finished = new TimeSpan(DateTime.Now.Ticks); } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/TestFailureInfo.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.TestRunners.Common; /// /// Contains information about a single test failure. Information is used at the end of the run to print /// summary of failures to logcat as well as put them in the results Bundle. is used /// only to generate unique index when storing information in the Bundle, so must contain /// the test name. /// public class TestFailureInfo { /// /// Gets or sets the name of the test. Must not be null or empty (all whitespace isn't allowed either) /// /// The name of the test. public string TestName { get; set; } /// /// Gets or sets the message. /// /// The message. public string Message { get; set; } /// /// Gets a value indicating whether this has info /// about failure. /// /// true info exists; otherwise, false. public bool HasInfo => !string.IsNullOrEmpty(TestName?.Trim()) && !string.IsNullOrEmpty(Message); } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/TestResult.cs ================================================ namespace Microsoft.DotNet.XHarness.TestRunners.Common; /// /// Enumeration used to state the result of a test. /// public enum TestResult { /// /// Test was executed and passed. /// Passed, /// /// Test was executed and failed. /// Failed, /// /// Test was not executed but was skipped. /// Skipped, } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/TestRunResult.cs ================================================ namespace Microsoft.DotNet.XHarness.TestRunners.Common; public struct TestRunResult { /// /// Retrieve the number of executed tests in a run. /// public long ExecutedTests { get; private set; } /// /// Retrieve the number of failed tests in a run. /// public long FailedTests { get; private set; } /// /// Retrieve the number of not executed tests due to the filters in a /// run. /// public long FilteredTests { get; private set; } /// /// Retrieve the number of inconclusive tests in a run. /// public long InconclusiveTests { get; private set; } /// /// Retrieve the number of passed tests in a run. /// public long PassedTests { get; private set; } /// /// Retrieve the number of skipped tests in a run. /// public long SkippedTests { get; private set; } /// /// Retrieve the total number of tests in a run. This value /// includes all skipped and filtered tests and might no be equal /// to the value returned by ExecutedTests. /// public long TotalTests { get; private set; } internal TestRunResult(TestRunner runner) { ExecutedTests = runner.ExecutedTests; FailedTests = runner.FailedTests; FilteredTests = runner.FilteredTests; InconclusiveTests = runner.InconclusiveTests; PassedTests = runner.PassedTests; SkippedTests = runner.SkippedTests; TotalTests = runner.TotalTests; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/TestRunSelector.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.TestRunners.Common; internal class TestRunSelector { public string Assembly { get; set; } public string Value { get; set; } public TestRunSelectorType Type { get; set; } public bool Include { get; set; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/TestRunSelectorType.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.TestRunners.Common; internal enum TestRunSelectorType { Assembly, Namespace, Class, Single, } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/TestRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; namespace Microsoft.DotNet.XHarness.TestRunners.Common; public abstract class TestRunner { /// /// Event raised when a test has started. /// public event EventHandler TestStarted; /// /// Event raised when a test has completed or has been skipped. /// public event EventHandler<(string TestName, TestResult TestResult)> TestCompleted; /// /// Number of inconclusive tests. /// public long InconclusiveTests { get; protected set; } = 0; /// /// Number of failed tests. /// public long FailedTests { get; protected set; } = 0; /// /// Number of successful tests. /// public long PassedTests { get; protected set; } = 0; /// /// Number of skipped tests. /// public long SkippedTests { get; protected set; } = 0; /// /// Number of executed tests, which can be the same of lower thant the /// number of total tests. /// public long ExecutedTests { get; protected set; } = 0; /// /// Total number of tests. This icludes, executed, ignored, filtered /// and skipped tests. /// public long TotalTests { get; protected set; } = 0; /// /// Number of tests that were not executed because they matched a /// filter. /// public long FilteredTests { get; protected set; } = 0; /// /// Specify if the runner should execute tests in parallel. Default is /// false. /// public bool RunInParallel { get; set; } = false; /// /// Root directory of the tests. /// public string TestsRootDirectory { get; set; } /// /// Specify if all the tests found in the assemblies should be ran. /// Default is true. /// public virtual bool RunAllTestsByDefault { get; set; } = true; /// /// Specify if the runner should log those tests that have been excluded /// due to a filter. Default is false. /// public bool LogExcludedTests { get; set; } /// /// TextWriter that will be used to write the results of the test run. /// public TextWriter Writer { get; set; } /// /// List that contains all the failures that occurred in the test run. /// public List FailureInfos { get; } = new List(); public bool ShowFailureInfos { get; set; } = true; /// /// Logging object. /// protected LogWriter Logger { get; } /// /// Name of the file that will be used to write the results. /// protected abstract string ResultsFileName { get; set; } protected TestRunner(LogWriter logger) { Logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public abstract Task Run(IEnumerable testAssemblies); public abstract Task WriteResultsToFile(XmlResultJargon xmlResultJargon); public abstract Task WriteResultsToFile(TextWriter writer, XmlResultJargon jargon); public abstract void SkipTests(IEnumerable tests); public abstract void SkipCategories(IEnumerable categories); public abstract void SkipMethod(string method, bool isExcluded); public abstract void SkipClass(string className, bool isExcluded); protected void OnError(string message) => Logger.OnError(message); protected void OnWarning(string message) => Logger.OnWarning(message); protected void OnDebug(string message) => Logger.OnDebug(message); protected void OnDiagnostic(string message) => Logger.OnDiagnostic(message); protected void OnInfo(string message) => Logger.OnInfo(message); protected void OnAssemblyStart(Assembly asm) { } protected void OnAssemblyFinish(Assembly asm) { } protected void LogFailureSummary() { if (!ShowFailureInfos || FailureInfos == null || FailureInfos.Count == 0) { return; } OnInfo("Failed tests:"); for (int i = 1; i <= FailureInfos.Count; i++) { TestFailureInfo info = FailureInfos[i - 1]; if (info == null || !info.HasInfo) { continue; } OnInfo($"{i}) {info.Message}"); } } protected virtual string GetResultsFilePath() { if (string.IsNullOrEmpty(ResultsFileName)) { throw new InvalidOperationException("Runner didn't specify a valid results file name"); } string resultsPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); if (!Directory.Exists(resultsPath)) { Directory.CreateDirectory(resultsPath); } return Path.Combine(resultsPath, ResultsFileName); } protected virtual void OnTestStarted(string testName) => TestStarted?.Invoke(this, testName); protected virtual void OnTestCompleted((string TestName, TestResult TestResult) result) => TestCompleted?.Invoke(this, result); } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/WasmApplicationEntryPointBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading.Tasks; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Common; public abstract class WasmApplicationEntryPointBase : ApplicationEntryPoint { protected override int? MaxParallelThreads => 1; protected override IDevice? Device => null; public override async Task RunAsync() { var options = ApplicationOptions.Current; var runner = await InternalRunAsync(options, null, Console.Out); LastRunHadFailedTests = runner.FailedTests != 0; // Emit coverage data on stdout for the host to capture if (CoverageResultPath != null) { try { var coverageBytes = System.IO.File.ReadAllBytes(CoverageResultPath); var base64 = Convert.ToBase64String(coverageBytes); Console.WriteLine($"STARTCOVERAGEXML {coverageBytes.Length} {base64} ENDCOVERAGEXML"); } catch (Exception ex) { Console.WriteLine($"[Coverage] Warning: Failed to emit coverage data: {ex.Message}"); } } } public bool LastRunHadFailedTests { get; set; } protected override void TerminateWithSuccess() => Environment.Exit(0); } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Common/iOSApplicationEntryPointBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading.Tasks; using System.IO; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Common; public abstract class iOSApplicationEntryPointBase : ApplicationEntryPoint { /// /// Logger used for outputting logs. Defaults to Console.Out. /// public TextWriter? Logger = Console.Out; /// /// The final path where test results in XML format will be saved. /// public string TestsResultsFinalPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "test-results.xml"); public override async Task RunAsync() { var options = ApplicationOptions.Current; // On iOS 18 and later, transferring results over a TCP tunnel isn’t supported. // Instead, copy the results file from the device to the host machine. if (!OperatingSystem.IsMacCatalyst() && Environment.OSVersion.Version.Major >= 18) { using TextWriter? resultsFileMaybe = options.EnableXml ? System.IO.File.CreateText(TestsResultsFinalPath) : null; await InternalRunAsync(options, Logger, resultsFileMaybe); Console.WriteLine($"Test results saved to: {TestsResultsFinalPath}"); if (CoverageResultPath != null) { Console.WriteLine($"Coverage results saved to: {CoverageResultPath}"); } } else { TcpTextWriter? writer; try { writer = options.UseTunnel ? TcpTextWriter.InitializeWithTunnelConnection(options.HostPort) : TcpTextWriter.InitializeWithDirectConnection(options.HostName, options.HostPort); } catch (Exception ex) { Console.WriteLine("Failed to initialize TCP writer. Continuing on console." + Environment.NewLine + ex); writer = null; // null means we will fall back to console output } using (writer) { var logger = (writer == null || options.EnableXml) ? new LogWriter(Device) : new LogWriter(Device, writer); logger.MinimumLogLevel = MinimumLogLevel.Info; await InternalRunAsync(options, writer, writer); // Coverage file is written to disk by CoverageManager (same as iOS 18+ path). // Do NOT send coverage data over TCP — it would corrupt the test results XML stream. // The host will pull the coverage file from the app container. if (CoverageResultPath != null) { Console.WriteLine($"Coverage results saved to: {CoverageResultPath}"); } } } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/FilterBuilder.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using NUnit.Engine; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.NUnit; /// /// Helper class that will build an NUnit v3 filter /// internal class FilterBuilder { private readonly ITestFilterBuilder _testFilterBuilder; private readonly bool _runAssemblyByDefault; public List IgnoredCategories { get; } = new List(); public List IgnoredClasses { get; } = new List(); public List IgnoredMethods { get; } = new List(); public FilterBuilder(ITestFilterBuilder testFilterBuilder, bool runAssemblyByDefault = true) { _testFilterBuilder = testFilterBuilder ?? throw new ArgumentNullException(nameof(testFilterBuilder)); _runAssemblyByDefault = runAssemblyByDefault; } internal string? BuildWhereClause() // does not need to be internal, doing it so that we can test it { // not the best api to add tests, we need to build the expression to be passed to the filter builder // to generate the tests. First, consider the case in which we are running all the tests, in that // case we want to make sure that we are || and == // For example: // test == "My.Skipped.Test" or cat == "outerloop" // // the above filter will skip any test that matches the full qualify named and the category outerloop. // On the other hand, when we are including them, we have to do the reverse: // test != "My.Skipped.Test" or cat != "outerloop" // // The above filter will skip all tests that do not have the given fully-qualified name of the category // build the comparison tuples, then use string.join with the or operation var comparisons = new List(); var filters = new Dictionary> { ["cat"] = IgnoredCategories, ["class"] = IgnoredClasses, ["test"] = IgnoredMethods, // we could use AddTest, but it will not allow us to do the !isExcluded }; foreach (string category in filters.Keys) { var filtersInCategory = filters[category]; foreach (var filterReason in filtersInCategory) { var eq = _runAssemblyByDefault ? "==" : "!="; comparisons.Add($"{category} {eq} {filterReason}"); } } return comparisons.Count == 0 ? null : string.Join(" or ", comparisons); } public TestFilter GetFilter() { var whereClause = BuildWhereClause(); if (!string.IsNullOrEmpty(whereClause)) { _testFilterBuilder.SelectWhere(BuildWhereClause()); } return _testFilterBuilder.GetFilter(); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/INUnitTestRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.TestRunners.Common; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.NUnit; /// /// Interface to be implemented by the runner so that the listener can interact with it. /// internal interface INUnitTestRunner { void IncreasePassedTests(); void IncreaseSkippedTests(); void IncreaseFailedTests(); void IncreaseInconclusiveTests(); void Add(TestFailureInfo info); bool GCAfterEachFixture { get; } string? TestsRootDirectory { get; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/IResultSummary.cs ================================================ using System.Collections.Generic; using NUnit.Engine; using NUnit.Framework.Interfaces; namespace Microsoft.DotNet.XHarness.TestRunners.NUnit; public interface IResultSummary : IList { string Name { get; } string FullName { get; } long InconclusiveTests { get; } long FailedTests { get; } long PassedTests { get; } long SkippedTests { get; } long ExecutedTests { get; } long TotalTests { get; } long FilteredTests { get; } long AssertCount { get; } double Duration { get; } TestStatus TestStatus { get; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/Microsoft.DotNet.XHarness.TestRunners.NUnit.csproj ================================================  $(XHarnessNetTFMs) true $(NoWarn);CS8002 disable ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/NUnit3XmlOutputWriter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Globalization; using System.IO; using System.Reflection; using System.Xml; using Microsoft.DotNet.XHarness.Common; using NUnit.Framework.Internal; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.NUnit; /// /// NUnit3XmlOutputWriter is responsible for writing the results /// of a test to a file in NUnit 3.0 format. /// internal class NUnit3XmlOutputWriter : OutputWriter { private readonly DateTime _runStartTime; private XmlWriter? _xmlWriter; public NUnit3XmlOutputWriter(DateTime runStartTime) => _runStartTime = runStartTime; /// /// Writes the test result to the specified TextWriter /// /// The result to be written to a file /// A TextWriter to which the result is written public override void WriteResultFile(IResultSummary result, TextWriter writer) { var xmlWriter = new XmlTextWriter(writer) { Formatting = Formatting.Indented }; try { WriteXmlOutput(result, xmlWriter); } finally { xmlWriter.Close(); } } private void WriteXmlOutput(IResultSummary result, XmlWriter xmlWriter) { _xmlWriter = xmlWriter; InitializeXmlFile(result); WriteResultElement(result); TerminateXmlFile(); } private void InitializeXmlFile(IResultSummary result) { if (_xmlWriter == null) // should never happen, would mean a programmers error { throw new InvalidOperationException("Null writer"); } _xmlWriter.WriteStartDocument(false); // In order to match the format used by NUnit 3.0, we // wrap the entire result from the framework in a // element. _xmlWriter.WriteStartElement("test-run"); _xmlWriter.WriteAttributeString("id", "2"); // TODO: Should not be hard-coded _xmlWriter.WriteAttributeString("name", result.Name); _xmlWriter.WriteAttributeString("fullname", result.FullName); _xmlWriter.WriteAttributeString("testcasecount", result.TotalTests.ToString()); _xmlWriter.WriteAttributeString("result", result.TestStatus.ToXmlResultValue(XmlResultJargon.NUnitV3)); _xmlWriter.WriteAttributeString("time", result.Duration.ToString()); _xmlWriter.WriteAttributeString("total", result.TotalTests.ToString()); _xmlWriter.WriteAttributeString("passed", result.PassedTests.ToString()); _xmlWriter.WriteAttributeString("failed", result.FailedTests.ToString()); _xmlWriter.WriteAttributeString("inconclusive", result.InconclusiveTests.ToString()); _xmlWriter.WriteAttributeString("skipped", result.SkippedTests.ToString()); _xmlWriter.WriteAttributeString("asserts", result.AssertCount.ToString()); _xmlWriter.WriteAttributeString("run-date", XmlConvert.ToString(_runStartTime, "yyyy-MM-dd")); _xmlWriter.WriteAttributeString("start-time", XmlConvert.ToString(_runStartTime, "HH:mm:ss")); _xmlWriter.WriteAttributeString("random-seed", Randomizer.InitialSeed.ToString()); WriteEnvironmentElement(); } private void WriteEnvironmentElement() { if (_xmlWriter == null) // should never happen, would mean a programmers error { throw new InvalidOperationException("Null writer"); } _xmlWriter.WriteStartElement("environment"); var assembly = Assembly.GetExecutingAssembly(); AssemblyName assemblyName = AssemblyHelper.GetAssemblyName(assembly); _xmlWriter.WriteAttributeString("nunit-version", assemblyName?.Version?.ToString() ?? "unknown"); _xmlWriter.WriteAttributeString("clr-version", Environment.Version.ToString()); _xmlWriter.WriteAttributeString("os-version", Environment.OSVersion.ToString()); _xmlWriter.WriteAttributeString("platform", Environment.OSVersion.Platform.ToString()); _xmlWriter.WriteAttributeString("cwd", Environment.CurrentDirectory); _xmlWriter.WriteAttributeString("machine-name", Environment.MachineName); _xmlWriter.WriteAttributeString("user", Environment.UserName); _xmlWriter.WriteAttributeString("user-domain", Environment.UserDomainName); _xmlWriter.WriteAttributeString("culture", CultureInfo.CurrentCulture.ToString()); _xmlWriter.WriteAttributeString("uiculture", CultureInfo.CurrentUICulture.ToString()); _xmlWriter.WriteEndElement(); } private void WriteResultElement(IResultSummary result) { if (_xmlWriter == null) // should never happen, would mean a programmer's error { throw new InvalidOperationException("Null writer"); } // much simpler than in other writers, we just need to get the child nodes of each of the test-run and write // them. NUnit3 already gave us the xml we need to use foreach (var testRun in result) { for (var i = 0; i < testRun.Result.ChildNodes.Count; i++) { var node = testRun.Result.ChildNodes[i]; if (node?.Name == "environment") { continue; } node?.WriteTo(_xmlWriter); } } } private void TerminateXmlFile() { if (_xmlWriter == null) // should never happen, would mean a programmer's error { throw new InvalidOperationException("Null writer"); } _xmlWriter.WriteEndElement(); // test-run _xmlWriter.WriteEndDocument(); _xmlWriter.Flush(); _xmlWriter.Close(); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/NUnitTestListener.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Text; using System.Xml; using Microsoft.DotNet.XHarness.TestRunners.Common; using NUnit; using NUnit.Engine; using NUnit.Framework.Interfaces; namespace Microsoft.DotNet.XHarness.TestRunners.NUnit; internal class NUnitTestListener : ITestEventListener { private readonly LogWriter _logger; private readonly INUnitTestRunner _runner; public NUnitTestListener(INUnitTestRunner runner, LogWriter logger) { _runner = runner ?? throw new ArgumentNullException(nameof(runner)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } private void TestStarted(XmlNode testEvent) { if (testEvent == null) { return; } if (!string.IsNullOrEmpty(_runner.TestsRootDirectory)) { Environment.CurrentDirectory = _runner.TestsRootDirectory; } _logger.OnInfo(testEvent.Attributes["fullname"].Value); } private void TestFinished(XmlNode testEvent) { // we need to get the status from the string. That value is stored in // value can be: Passed, Failed, Inconclusive or Skipped. To make things // 'simpler' we also need to check the label, which might have the following values: Error, Cancelled or Invalid var result = testEvent.GetAttribute("label") ?? testEvent.GetAttribute("result"); TestStatus status = result switch { "Passed" => TestStatus.Passed, "Failed" => TestStatus.Failed, "Inconclusive" => TestStatus.Inconclusive, "Skipped" => TestStatus.Skipped, _ => TestStatus.Inconclusive // Cancelled, error or invalid }; var testName = testEvent.Attributes["fullname"].Value; var sb = new StringBuilder(); switch (status) { case TestStatus.Passed: sb.Append("\t[PASS] "); _runner.IncreasePassedTests(); break; case TestStatus.Skipped: sb.Append("\t[IGNORED] "); _runner.IncreaseSkippedTests(); break; case TestStatus.Failed: sb.Append("\t[FAIL] "); _runner.IncreaseFailedTests(); break; case TestStatus.Inconclusive: sb.Append("\t[INCONCLUSIVE] "); _runner.IncreaseInconclusiveTests(); break; default: sb.Append("\t[INFO] "); break; } sb.Append(testName); // if we skipped or we failed, add some extra info to the logging: if (status == TestStatus.Failed) { var messageNodes = testEvent.SelectNodes("failure/message"); if (messageNodes.Count == 1) { if (messageNodes[0].ChildNodes.Count == 1) // should not happen, but I trust no one { var cDataNode = messageNodes[0].ChildNodes[0]; var message = cDataNode?.InnerText?.Trim(); if (!string.IsNullOrEmpty(message)) { message = message.Replace("\r\n", "\\r\\n"); sb.Append($" : {message}"); } } } // get the stack trace, similar to the message node var stacktraceNodes = testEvent.SelectNodes("failure/stack-trace"); if (stacktraceNodes.Count == 1) { if (stacktraceNodes[0].ChildNodes.Count == 1) // should not happen, but I trust no one { var cDataNode = messageNodes[0].ChildNodes[0]; var stackTrace = cDataNode?.InnerText?.Trim(); if (!string.IsNullOrEmpty(stackTrace)) { stackTrace = stackTrace.Replace("\r\n", "\\r\\n"); string[] lines = stackTrace.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string line in lines) { sb.AppendLine($"\t\t{line}"); } } } } _runner.Add(new TestFailureInfo { TestName = testName, Message = sb.ToString() }); } _logger.OnInfo(sb.ToString()); } public void OnTestEvent(string report) { // again, not a simple api, the report string is an xml, that contains a fragment of xml // which depends on the event type, load the xml, do the appropriate thing. var doc = new XmlDocument(); doc.LoadXml(report); var testEvent = doc.FirstChild; switch (testEvent.Name) { case "start-test": TestStarted(testEvent); break; case "test-case": TestFinished(testEvent); break; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/NUnitTestRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.TestRunners.Common; using NUnit.Engine; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.NUnit; internal class NUnitTestRunner : TestRunner, INUnitTestRunner { private readonly FilterBuilder _testFilterBuilder; private readonly NUnitTestListener _testListener; private ResultSummary? _results; private bool _runAssemblyByDefault; public NUnitTestRunner(LogWriter logger) : base(logger) { _testListener = new NUnitTestListener(this, logger); _testFilterBuilder = new FilterBuilder(new TestFilterBuilder()); } private Dictionary? AssemblyFilters { get; set; } protected override string ResultsFileName { get; set; } = "TestResults.NUnit.xml"; public bool GCAfterEachFixture { get; set; } public void IncreasePassedTests() { PassedTests++; ExecutedTests++; } public void IncreaseSkippedTests() => SkippedTests++; public void IncreaseFailedTests() { FailedTests++; ExecutedTests++; } public void IncreaseInconclusiveTests() { InconclusiveTests++; ExecutedTests++; } public void Add(TestFailureInfo info) => FailureInfos.Add(info ?? throw new ArgumentNullException(nameof(info))); public override async Task Run(IEnumerable testAssemblies) { if (testAssemblies == null) { throw new ArgumentNullException(nameof(testAssemblies)); } if (AssemblyFilters == null || AssemblyFilters.Count == 0) { _runAssemblyByDefault = true; } else { _runAssemblyByDefault = AssemblyFilters.Values.Any(v => !v); } ITestEngine engine = TestEngineActivator.CreateInstance(); TestFilter filter = _testFilterBuilder.GetFilter(); // use the current executing assembly full name as the name of the test suit that groups all the diff assemblies _results = new ResultSummary(Assembly.GetExecutingAssembly().FullName!, this); TotalTests = 0; foreach (TestAssemblyInfo assemblyInfo in testAssemblies) { if (assemblyInfo == null || assemblyInfo.Assembly == null || !ShouldRunAssembly(assemblyInfo)) { continue; } var testPackage = new TestPackage(assemblyInfo.FullPath); ITestRunner runner = engine.GetRunner(testPackage); TotalTests += runner.CountTestCases(filter); ITestRun result; try { OnAssemblyStart(assemblyInfo.Assembly); result = await Task.Run(() => runner.RunAsync(_testListener, filter)).ConfigureAwait(false); } finally { OnAssemblyFinish(assemblyInfo.Assembly); } if (result == null) { continue; } _results.Add(result); } FilteredTests = TotalTests - ExecutedTests; LogFailureSummary(); } private bool ShouldRunAssembly(TestAssemblyInfo assemblyInfo) { if (assemblyInfo == null) { return false; } if (AssemblyFilters == null || AssemblyFilters.Count == 0) { return true; } if (AssemblyFilters.TryGetValue(assemblyInfo.FullPath, out bool include)) { return ReportFilteredAssembly(assemblyInfo, include); } string fileName = Path.GetFileName(assemblyInfo.FullPath); if (AssemblyFilters.TryGetValue(fileName, out include)) { return ReportFilteredAssembly(assemblyInfo, include); } fileName = Path.GetFileNameWithoutExtension(assemblyInfo.FullPath); if (AssemblyFilters.TryGetValue(fileName, out include)) { return ReportFilteredAssembly(assemblyInfo, include); } return _runAssemblyByDefault; } private bool ReportFilteredAssembly(TestAssemblyInfo assemblyInfo, bool include) { if (!LogExcludedTests) { return include; } const string included = "Included"; const string excluded = "Excluded"; OnInfo($"[FILTER] {(include ? included : excluded)} assembly: {assemblyInfo.FullPath}"); return include; } public override Task WriteResultsToFile(XmlResultJargon jargon) { if (_results == null) { return Task.FromResult(string.Empty); } string ret = GetResultsFilePath(); if (string.IsNullOrEmpty(ret)) { return Task.FromResult(string.Empty); } jargon.GetWriter().WriteResultFile(_results, ret); return Task.FromResult(ret); } public override Task WriteResultsToFile(TextWriter writer, XmlResultJargon jargon) { if (_results == null) { return Task.CompletedTask; } jargon.GetWriter().WriteResultFile(_results, writer); return Task.CompletedTask; } public override void SkipTests(IEnumerable tests) => _testFilterBuilder.IgnoredMethods.AddRange(tests); public override void SkipCategories(IEnumerable categories) => _testFilterBuilder.IgnoredCategories.AddRange(categories); public override void SkipMethod(string method, bool _) => _testFilterBuilder.IgnoredMethods.Add(method); public override void SkipClass(string className, bool _) => _testFilterBuilder.IgnoredClasses.Add(className); } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/OutputWriter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.IO; using System.Text; namespace Microsoft.DotNet.XHarness.TestRunners.NUnit; /// /// OutputWriter is an abstract class used to write test /// results to a file in various formats. Specific /// OutputWriters are derived from this class. /// internal abstract class OutputWriter { /// /// Writes a test result to a file /// /// The result to be written /// Path to the file to which the result is written public void WriteResultFile(IResultSummary result, string outputPath) { using var writer = new StreamWriter(outputPath, false, Encoding.UTF8); WriteResultFile(result, writer); } /// /// Abstract method that writes a test result to a TextWriter /// /// The result to be written /// A TextWriter to which the result is written public abstract void WriteResultFile(IResultSummary result, TextWriter writer); } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/Properties/AssemblyInfo.cs ================================================ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.DotNet.XHarness.TestRunners.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/ResultSummary.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.TestRunners.Common; using NUnit.Engine; using NUnit.Framework.Interfaces; namespace Microsoft.DotNet.XHarness.TestRunners.NUnit; /// /// Helper class used to summarize the result of a test run. /// internal class ResultSummary : List, IResultSummary { private readonly TestRunner _runner; private double? _duration; private long? _assertCount; public string Name { get; private set; } public string FullName => Name; public long InconclusiveTests => _runner.InconclusiveTests; public long FailedTests => _runner.FilteredTests; public long PassedTests => _runner.PassedTests; public long SkippedTests => _runner.SkippedTests; public long ExecutedTests => _runner.ExecutedTests; public long TotalTests => _runner.TotalTests; public long FilteredTests => _runner.FilteredTests; public long AssertCount { get { if (_assertCount.HasValue) { return _assertCount.Value; } // not super efficient GetSummaryData(); return _assertCount!.Value; } } public double Duration { get { if (_duration.HasValue) { return _duration.Value; } // not very efficient, but we should not cate too much GetSummaryData(); return _duration!.Value; } } private void GetSummaryData() { double duration = 0; long assertCount = 0; foreach (var result in this) { var testRunNode = result.Result.FirstChild; if (testRunNode.Name != "test-run") { continue; } if (double.TryParse(testRunNode.Attributes["time"].Value, out var time)) { duration += time; } if (long.TryParse(testRunNode.Attributes["asserts"].Value, out var asserts)) { assertCount += asserts; } } _duration = duration; _assertCount = assertCount; } public TestStatus TestStatus { get { return FailedTests > 0 ? TestStatus.Failed : TestStatus.Passed; } } public ResultSummary(string testSuite, TestRunner testRunner) : base() { Name = testSuite ?? throw new ArgumentNullException(nameof(testSuite)); _runner = testRunner ?? throw new ArgumentNullException(nameof(testRunner)); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/TestStatusExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.Common; using NUnit.Framework.Interfaces; namespace Microsoft.DotNet.XHarness.TestRunners.NUnit; internal static class TestStatusExtensions { public static string ToXmlResultValue(this TestStatus status, XmlResultJargon jargon) => jargon switch { XmlResultJargon.NUnitV2 => status switch { TestStatus.Failed => "Failure", TestStatus.Inconclusive => "Inconclusive", TestStatus.Passed => "Success", TestStatus.Skipped => "Ignored", _ => "Failure" }, XmlResultJargon.NUnitV3 => status switch { TestStatus.Failed => "Failed", TestStatus.Inconclusive => "Inconclusive", TestStatus.Passed => "Passed", TestStatus.Skipped => "Skipped", _ => "Failed", }, XmlResultJargon.xUnit => status switch { TestStatus.Failed => "Fail", TestStatus.Inconclusive => "Skip", TestStatus.Passed => "Pass", TestStatus.Skipped => "Skip", _ => "Fail", }, _ => throw new ArgumentOutOfRangeException(nameof(status)), }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.NUnit/XmlResultJargonExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.Common; namespace Microsoft.DotNet.XHarness.TestRunners.NUnit; internal static class XmlResultJargonExtensions { public static OutputWriter GetWriter(this XmlResultJargon jargon) => jargon switch { XmlResultJargon.NUnitV2 => throw new NotImplementedException(), XmlResultJargon.NUnitV3 => new NUnit3XmlOutputWriter(DateTime.UtcNow), XmlResultJargon.xUnit => throw new NotImplementedException(), _ => throw new InvalidOperationException($"Jargon {jargon} is not supported by this runner.") }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/AndroidApplicationEntryPoint.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; using Microsoft.DotNet.XHarness.TestRunners.Common; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; public abstract class AndroidApplicationEntryPoint : AndroidApplicationEntryPointBase { protected override bool IsXunit => true; protected override TestRunner GetTestRunner(LogWriter logWriter) { if (!RuntimeFeature.IsDynamicCodeSupported) { var reflectionRunner = new ReflectionBasedXunitTestRunner(logWriter) { MaxParallelThreads = MaxParallelThreads }; ConfigureRunnerFilters(reflectionRunner, ApplicationOptions.Current); return reflectionRunner; } var runner = new XUnitTestRunner(logWriter) { MaxParallelThreads = MaxParallelThreads }; ConfigureRunnerFilters(runner, ApplicationOptions.Current); return runner; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/CompletionCallbackExecutionSink.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; using Xunit; using Xunit.Abstractions; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; internal class CompletionCallbackExecutionSink : global::Xunit.Sdk.LongLivedMarshalByRefObject, IExecutionSink { private readonly Action _completionCallback; private readonly IExecutionSink _innerSink; public ExecutionSummary ExecutionSummary => _innerSink.ExecutionSummary; public ManualResetEvent Finished => _innerSink.Finished; public CompletionCallbackExecutionSink(IExecutionSink innerSink, Action completionCallback) { _innerSink = innerSink; _completionCallback = completionCallback; } public void Dispose() => _innerSink.Dispose(); public bool OnMessageWithTypes(IMessageSinkMessage message, HashSet messageTypes) { var result = _innerSink.OnMessageWithTypes(message, messageTypes); message.Dispatch(messageTypes, args => _completionCallback(ExecutionSummary)); return result; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/CustomXunitTestRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.DotNet.XHarness.TestRunners.Common; using Xunit; using Xunit.Abstractions; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; /// /// Abstract xunit test runner that uses reflection-based discovery (NativeAOT-safe). /// Concrete implementations define configuration (parallelism) and result output behavior. /// internal abstract class CustomXunitTestRunner : XunitTestRunnerBase { protected CustomXunitTestRunner(LogWriter logger) : base(logger) { ShowFailureInfos = false; } protected abstract string RunnerDisplayName { get; } private protected XElement? _assembliesElement; internal XElement ConsumeAssembliesElement() { Debug.Assert(_assembliesElement != null, "ConsumeAssembliesElement called before Run() or after ConsumeAssembliesElement() was already called."); var res = _assembliesElement; _assembliesElement = null; FailureInfos.Clear(); return res!; } protected abstract TestAssemblyConfiguration CreateConfiguration(); public override async Task Run(IEnumerable testAssemblies) { OnInfo($"Using {RunnerDisplayName}"); _assembliesElement = new XElement("assemblies"); var configuration = CreateConfiguration(); var discoveryOptions = TestFrameworkOptions.ForDiscovery(configuration); var discoverySink = new TestDiscoverySink(); var diagnosticSink = new ConsoleDiagnosticMessageSink(Logger); var testOptions = TestFrameworkOptions.ForExecution(configuration); var testSink = new TestMessageSink(); var totalSummary = new ExecutionSummary(); foreach (var testAsmInfo in testAssemblies) { string assemblyFileName = testAsmInfo.FullPath; var controller = new YieldingXunit2(AppDomainSupport.Denied, new NullSourceInformationProvider(), assemblyFileName, configFileName: null, shadowCopy: false, shadowCopyFolder: null, diagnosticMessageSink: diagnosticSink, verifyTestAssemblyExists: false); discoveryOptions.SetSynchronousMessageReporting(true); testOptions.SetSynchronousMessageReporting(true); OnInfo($"Discovering: {assemblyFileName} (method display = {discoveryOptions.GetMethodDisplayOrDefault()}, method display options = {discoveryOptions.GetMethodDisplayOptionsOrDefault()})"); var assemblyInfo = new global::Xunit.Sdk.ReflectionAssemblyInfo(testAsmInfo.Assembly); var discoverer = new ThreadlessXunitDiscoverer(assemblyInfo, new NullSourceInformationProvider(), discoverySink); discoverer.FindWithoutThreads(includeSourceInformation: false, discoverySink, discoveryOptions); var testCasesToRun = discoverySink.TestCases.Where(t => !_filters.IsExcluded(t)).ToList(); OnInfo($"Discovered: {assemblyFileName} (found {testCasesToRun.Count} of {discoverySink.TestCases.Count} test cases)"); var summaryTaskSource = new TaskCompletionSource(); var resultsXmlAssembly = new XElement("assembly"); #pragma warning disable CS0618 // Delegating*Sink types are marked obsolete, but we can't move to ExecutionSink yet: https://github.com/dotnet/arcade/issues/14375 var resultsSink = new DelegatingXmlCreationSink(new DelegatingExecutionSummarySink(testSink), resultsXmlAssembly); #pragma warning restore var completionSink = new CompletionCallbackExecutionSink(resultsSink, summary => summaryTaskSource.SetResult(summary)); if (EnvironmentVariables.IsLogTestStart()) { testSink.Execution.TestStartingEvent += args => { OnInfo($"[STRT] {args.Message.Test.DisplayName}"); }; } testSink.Execution.TestPassedEvent += args => { OnDebug($"[PASS] {args.Message.Test.DisplayName}"); PassedTests++; }; testSink.Execution.TestSkippedEvent += args => { OnDebug($"[SKIP] {args.Message.Test.DisplayName}"); SkippedTests++; }; testSink.Execution.TestFailedEvent += args => { OnError($"[FAIL] {args.Message.Test.DisplayName}{Environment.NewLine}{ExceptionUtility.CombineMessages(args.Message)}{Environment.NewLine}{ExceptionUtility.CombineStackTraces(args.Message)}"); FailedTests++; }; testSink.Execution.TestFinishedEvent += args => ExecutedTests++; testSink.Execution.TestAssemblyStartingEvent += args => { Console.WriteLine($"Starting: {assemblyFileName}"); }; testSink.Execution.TestAssemblyFinishedEvent += args => { Console.WriteLine($"Finished: {assemblyFileName}"); }; await controller.RunTestsAsync(testCasesToRun, MessageSinkAdapter.Wrap(completionSink), testOptions); totalSummary = Combine(totalSummary, await summaryTaskSource.Task); _assembliesElement.Add(resultsXmlAssembly); } TotalTests = totalSummary.Total; } private ExecutionSummary Combine(ExecutionSummary aggregateSummary, ExecutionSummary assemblySummary) { return new ExecutionSummary { Total = aggregateSummary.Total + assemblySummary.Total, Failed = aggregateSummary.Failed + assemblySummary.Failed, Skipped = aggregateSummary.Skipped + assemblySummary.Skipped, Errors = aggregateSummary.Errors + assemblySummary.Errors, Time = aggregateSummary.Time + assemblySummary.Time }; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/EnvironmentVariables.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; internal static class EnvironmentVariables { public static bool IsTrue(string varName) => Environment.GetEnvironmentVariable(varName)?.ToLower().Equals("true") ?? false; public static bool IsLogTestStart() => IsTrue("XHARNESS_LOG_TEST_START"); public static bool IsLogThreadId() => IsTrue("XHARNESS_LOG_THREAD_ID"); } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/Microsoft.DotNet.XHarness.TestRunners.Xunit.csproj ================================================  $(XHarnessNetTFMs) true disable ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/NUnit3Xml.xslt ================================================  Failed Passed Failed Passed Failed Passed Failed Passed Skipped ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/NUnitXml.xslt ================================================ False True Failure Success Failure Success False True Failure Success False True False True Failure Success Skipped False True ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/Properties/AssemblyInfo.cs ================================================ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.DotNet.XHarness.TestRunners.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/ReflectionBasedXunitTestRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Threading.Tasks; using System.Xml; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.TestRunners.Common; using Xunit; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; /// /// Xunit test runner using reflection-based discovery (NativeAOT-safe) /// with parallel test execution support and file-based result output. /// internal class ReflectionBasedXunitTestRunner : CustomXunitTestRunner { public int? MaxParallelThreads { get; set; } public ReflectionBasedXunitTestRunner(LogWriter logger) : base(logger) { } protected override string RunnerDisplayName => "reflection-based Xunit runner (threaded execution)"; private string _resultsFileName = "TestResults.xUnit.xml"; protected override string ResultsFileName { get => _resultsFileName; set => _resultsFileName = value; } protected override TestAssemblyConfiguration CreateConfiguration() { int maxThreads = MaxParallelThreads ?? Environment.ProcessorCount; return new TestAssemblyConfiguration() { ShadowCopy = false, ParallelizeAssembly = false, ParallelizeTestCollections = RunInParallel, MaxParallelThreads = RunInParallel ? maxThreads : 1, PreEnumerateTheories = false, }; } public override Task WriteResultsToFile(XmlResultJargon xmlResultJargon) { if (_assembliesElement is null) return Task.FromResult(string.Empty); string outputFilePath = GetResultsFilePath(); var settings = new XmlWriterSettings { Indent = true }; using (var xmlWriter = XmlWriter.Create(outputFilePath, settings)) { _assembliesElement.Save(xmlWriter); } return Task.FromResult(outputFilePath); } public override Task WriteResultsToFile(TextWriter writer, XmlResultJargon jargon) { if (_assembliesElement is null) return Task.CompletedTask; var settings = new XmlWriterSettings { Indent = true }; using (var xmlWriter = XmlWriter.Create(writer, settings)) { _assembliesElement.Save(xmlWriter); } return Task.CompletedTask; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/TestCaseExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Xunit.Abstractions; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; /// /// Useful extensions that make working with the ITestCase interface nicer within the runner. /// public static class TestCaseExtensions { /// /// Returns boolean indicating whether the test case does have traits. /// /// The test case under test. /// true if the test case has traits, false otherwise. public static bool HasTraits(this ITestCase testCase) => testCase.Traits != null && testCase.Traits.Count > 0; public static bool TryGetTrait(this ITestCase testCase, string trait, [NotNullWhen(true)] out List? values, StringComparison comparer = StringComparison.InvariantCultureIgnoreCase) { if (trait == null) { values = null; return false; } // there is no guarantee that the dict created by xunit is case insensitive, therefore, trygetvalue might // not return the value we are interested in. We have to loop, which is not ideal, but will be better // for our use case. foreach (var t in testCase.Traits.Keys) { if (trait.Equals(t, comparer)) { return testCase.Traits.TryGetValue(t, out values); } } values = null; return false; } /// /// Get the name of the test class that owns the test case. /// /// TestCase whose class we want to retrieve. /// The name of the class that owns the test. public static string? GetTestClass(this ITestCase testCase) => testCase.TestMethod?.TestClass?.Class?.Name?.Trim(); public static string? GetNamespace(this ITestCase testCase) { var testClassName = testCase.GetTestClass(); if (testClassName == null) { return null; } int dot = testClassName.LastIndexOf('.'); return dot <= 0 ? null : testClassName.Substring(0, dot); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/ThreadlessXunitTestRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.TestRunners.Common; using Xunit; using Xunit.Abstractions; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; internal class ThreadlessXunitTestRunner : CustomXunitTestRunner { public ThreadlessXunitTestRunner(LogWriter logger) : base(logger) { } protected override string RunnerDisplayName => "threadless Xunit runner"; protected override string ResultsFileName { get => string.Empty; set => throw new InvalidOperationException("This runner outputs its results to stdout."); } protected override TestAssemblyConfiguration CreateConfiguration() { return new TestAssemblyConfiguration() { ShadowCopy = false, ParallelizeAssembly = false, ParallelizeTestCollections = false, MaxParallelThreads = 1, PreEnumerateTheories = false, }; } public override async Task WriteResultsToFile(XmlResultJargon xmlResultJargon) { Debug.Assert(xmlResultJargon == XmlResultJargon.xUnit); await WriteResultsToFile(Console.Out, xmlResultJargon); return ""; } public override async Task WriteResultsToFile(TextWriter writer, XmlResultJargon jargon) { await WasmXmlResultWriter.WriteResultsToFile(ConsumeAssembliesElement()); } } internal class ThreadlessXunitDiscoverer : global::Xunit.Sdk.XunitTestFrameworkDiscoverer { public ThreadlessXunitDiscoverer(IAssemblyInfo assemblyInfo, ISourceInformationProvider sourceProvider, IMessageSink diagnosticMessageSink) : base(assemblyInfo, sourceProvider, diagnosticMessageSink) { } public void FindWithoutThreads(bool includeSourceInformation, IMessageSink discoveryMessageSink, ITestFrameworkDiscoveryOptions discoveryOptions) { #pragma warning disable CS0618 // SynchronousMessageBus ctor is marked obsolete using (var messageBus = new global::Xunit.Sdk.SynchronousMessageBus(discoveryMessageSink)) #pragma warning restore { foreach (var type in AssemblyInfo.GetTypes(includePrivateTypes: false).Where(IsValidTestClass)) { var testClass = CreateTestClass(type); if (!FindTestsForType(testClass, includeSourceInformation, messageBus, discoveryOptions)) { break; } } messageBus.QueueMessage(new global::Xunit.Sdk.DiscoveryCompleteMessage()); } } } internal class ConsoleDiagnosticMessageSink(LogWriter logger) : global::Xunit.Sdk.LongLivedMarshalByRefObject, IMessageSink { public bool OnMessage(IMessageSinkMessage message) { if (message is IDiagnosticMessage diagnosticMessage) { logger.OnDebug(diagnosticMessage.Message); } return true; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/WasmApplicationEntryPoint.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.TestRunners.Common; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; public abstract class WasmApplicationEntryPoint : WasmApplicationEntryPointBase { protected virtual string TestAssembly { get; set; } = ""; protected virtual IEnumerable ExcludedTraits { get; set; } = Array.Empty(); protected virtual IEnumerable IncludedTraits { get; set; } = Array.Empty(); protected virtual IEnumerable IncludedClasses { get; set; } = Array.Empty(); protected virtual IEnumerable IncludedMethods { get; set; } = Array.Empty(); protected virtual IEnumerable IncludedNamespaces { get; set; } = Array.Empty(); protected override bool IsXunit => true; protected bool IsThreadless { get; set; } = true; protected bool RunInParallel { get; set; } = false; protected override TestRunner GetTestRunner(LogWriter logWriter) { XunitTestRunnerBase runner = IsThreadless ? new ThreadlessXunitTestRunner(logWriter) : new WasmThreadedTestRunner(logWriter) { MaxParallelThreads = MaxParallelThreads }; ConfigureRunnerFilters(runner, ApplicationOptions.Current); runner.RunInParallel = RunInParallel; runner.SkipCategories(ExcludedTraits); runner.SkipCategories(IncludedTraits, isExcluded: false); foreach (var cls in IncludedClasses) { runner.SkipClass(cls, false); } foreach (var method in IncludedMethods) { runner.SkipMethod(method, false); } foreach (var ns in IncludedNamespaces) { runner.SkipNamespace(ns, isExcluded: false); } return runner; } protected override IEnumerable GetTestAssemblies() => new[] { new TestAssemblyInfo(Assembly.LoadFrom(TestAssembly), TestAssembly) }; public async Task Run() { await RunAsync(); return LastRunHadFailedTests ? 1 : 0; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/WasmThreadedTestRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.TestRunners.Common; using Xunit; using Xunit.Abstractions; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; internal class WasmThreadedTestRunner : XUnitTestRunner { public WasmThreadedTestRunner(LogWriter logger) : base(logger) { TestStagePrefix = string.Empty; ShowFailureInfos = false; } protected override string ResultsFileName { get => string.Empty; set => throw new InvalidOperationException("This runner outputs its results to stdout."); } public override Task Run(IEnumerable testAssemblies) { OnInfo("Using threaded Xunit runner"); return base.Run(testAssemblies); } public override Task WriteResultsToFile(TextWriter writer, XmlResultJargon jargon) => WasmXmlResultWriter.WriteResultsToFile(ConsumeAssembliesElement()); } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/WasmXmlResultWriter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Net.Http; using System.Net.Http.Headers; using System.Runtime.InteropServices.JavaScript; using System.Threading.Tasks; using System.Xml.Linq; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; internal class WasmXmlResultWriter { public async static Task WriteResultsToFile(XElement assembliesElement) { if (OperatingSystem.IsBrowser()) { await Task.Yield(); GC.Collect(); await Task.Yield(); GC.Collect(); } using var ms = new MemoryStream(); assembliesElement.Save(ms); assembliesElement = null!; if (OperatingSystem.IsBrowser()) { await Task.Yield(); GC.Collect(); try { using JSObject globalThis = JSHost.GlobalThis; if (globalThis.HasProperty("fetch") && globalThis.HasProperty("location") && globalThis.HasProperty("document")) { ms.Position = 0; // globalThis.location.origin using JSObject location = globalThis.GetPropertyAsJSObject("location")!; var originURL = location.GetPropertyAsString("origin"); using var req = new HttpRequestMessage(HttpMethod.Post, originURL + "/test-results"); req.Content = new StreamContent(ms); req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/xml"); req.Content.Headers.ContentLength = ms.Length; using var httpClient = new HttpClient(); using var response = await httpClient.SendAsync(req); if (response.IsSuccessStatusCode) { Console.WriteLine($"Finished uploading {ms.Length} bytes of RESULTXML"); return; } // otherwise fall back to the console output } } catch (Exception) { // fall back to the console output } } ms.TryGetBuffer(out var bytes); var base64 = Convert.ToBase64String(bytes, Base64FormattingOptions.None); Console.WriteLine($"STARTRESULTXML {bytes.Count} {base64} ENDRESULTXML"); Console.WriteLine($"Finished writing {bytes.Count} bytes of RESULTXML"); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/XUnitFilter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Linq; using System.Text; using Microsoft.DotNet.XHarness.TestRunners.Common; using Xunit.Abstractions; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; internal class XUnitFilter { public string? AssemblyName { get; private set; } public string? SelectorName { get; private set; } public string? SelectorValue { get; private set; } public bool Exclude { get; private set; } public XUnitFilterType FilterType { get; private set; } public static XUnitFilter CreateSingleFilter(string singleTestName, bool exclude, string? assemblyName = null) { if (string.IsNullOrEmpty(singleTestName)) { throw new ArgumentException("must not be null or empty", nameof(singleTestName)); } return new XUnitFilter { AssemblyName = assemblyName, SelectorValue = singleTestName, FilterType = XUnitFilterType.Single, Exclude = exclude }; } public static XUnitFilter CreateAssemblyFilter(string assemblyName, bool exclude) { if (string.IsNullOrEmpty(assemblyName)) { throw new ArgumentException("must not be null or empty", nameof(assemblyName)); } // ensure that the assembly name does have one of the valid extensions var fileExtension = Path.GetExtension(assemblyName); if (fileExtension != ".dll" && fileExtension != ".exe") { throw new ArgumentException($"Assembly name must have .dll or .exe as extensions. Found extension {fileExtension}"); } return new XUnitFilter { AssemblyName = assemblyName, FilterType = XUnitFilterType.Assembly, Exclude = exclude }; } public static XUnitFilter CreateNamespaceFilter(string namespaceName, bool exclude, string? assemblyName = null) { if (string.IsNullOrEmpty(namespaceName)) { throw new ArgumentException("must not be null or empty", nameof(namespaceName)); } return new XUnitFilter { AssemblyName = assemblyName, SelectorValue = namespaceName, FilterType = XUnitFilterType.Namespace, Exclude = exclude }; } public static XUnitFilter CreateClassFilter(string className, bool exclude, string? assemblyName = null) { if (string.IsNullOrEmpty(className)) { throw new ArgumentException("must not be null or empty", nameof(className)); } return new XUnitFilter { AssemblyName = assemblyName, SelectorValue = className, FilterType = XUnitFilterType.TypeName, Exclude = exclude }; } public static XUnitFilter CreateTraitFilter(string traitName, string? traitValue, bool exclude) { if (string.IsNullOrEmpty(traitName)) { throw new ArgumentException("must not be null or empty", nameof(traitName)); } return new XUnitFilter { AssemblyName = null, SelectorName = traitName, SelectorValue = traitValue ?? string.Empty, FilterType = XUnitFilterType.Trait, Exclude = exclude }; } private bool ApplyTraitFilter(ITestCase testCase, Func? reportFilteredTest = null) { Func log = (result) => reportFilteredTest?.Invoke(result) ?? result; if (!testCase.HasTraits()) { return log(!Exclude); } if (testCase.TryGetTrait(SelectorName!, out var values)) { if (values == null || values.Count == 0) { // We have no values and the filter doesn't specify one - that means we match on // the trait name only. if (string.IsNullOrEmpty(SelectorValue)) { return log(Exclude); } return log(!Exclude); } return values.Any(value => value.Equals(SelectorValue, StringComparison.InvariantCultureIgnoreCase)) ? log(Exclude) : log(!Exclude); } // no traits found, that means that we return the opposite of the setting of the filter return log(!Exclude); } private bool ApplyTypeNameFilter(ITestCase testCase, Func? reportFilteredTest = null) { Func log = (result) => reportFilteredTest?.Invoke(result) ?? result; var testClassName = testCase.GetTestClass(); if (!string.IsNullOrEmpty(testClassName)) { if (string.Equals(testClassName, SelectorValue, StringComparison.InvariantCulture)) { return log(Exclude); } } return log(!Exclude); } private bool ApplySingleFilter(ITestCase testCase, Func? reportFilteredTest = null) { Func log = (result) => reportFilteredTest?.Invoke(result) ?? result; if (string.Equals(testCase.DisplayName, SelectorValue, StringComparison.InvariantCulture)) { // if there is a match, return the exclude value return log(Exclude); } // if there is not match, return the opposite return log(!Exclude); } private bool ApplyNamespaceFilter(ITestCase testCase, Func? reportFilteredTest = null) { Func log = (result) => reportFilteredTest?.Invoke(result) ?? result; var testClassNamespace = testCase.GetNamespace(); if (string.IsNullOrEmpty(testClassNamespace)) { // if we exclude, since we have no namespace, we include the test return log(!Exclude); } if (string.Equals(testClassNamespace, SelectorValue, StringComparison.InvariantCultureIgnoreCase)) { return log(Exclude); } // same logic as with no namespace return log(!Exclude); } public bool IsExcluded(TestAssemblyInfo assembly, Action? reportFilteredAssembly = null) { if (FilterType != XUnitFilterType.Assembly) { throw new InvalidOperationException("Filter is not targeting assemblies."); } Func log = (result) => ReportFilteredAssembly(assembly, result, reportFilteredAssembly); if (string.Equals(AssemblyName, assembly.FullPath, StringComparison.Ordinal)) { return log(Exclude); } string fileName = Path.GetFileName(assembly.FullPath); if (string.Equals(fileName, AssemblyName, StringComparison.Ordinal)) { return log(Exclude); } // No path of the name matched the filter, therefore return the opposite of the Exclude value return log(!Exclude); } public bool IsExcluded(ITestCase testCase, Action? log = null) { Func? reportFilteredTest = null; if (log != null) { reportFilteredTest = (result) => ReportFilteredTest(testCase, result, log); } return FilterType switch { XUnitFilterType.Trait => ApplyTraitFilter(testCase, reportFilteredTest), XUnitFilterType.TypeName => ApplyTypeNameFilter(testCase, reportFilteredTest), XUnitFilterType.Single => ApplySingleFilter(testCase, reportFilteredTest), XUnitFilterType.Namespace => ApplyNamespaceFilter(testCase, reportFilteredTest), _ => throw new InvalidOperationException($"Unsupported filter type {FilterType}") }; } private bool ReportFilteredTest(ITestCase testCase, bool excluded, Action? log = null) { const string includedText = "Included"; const string excludedText = "Excluded"; if (log == null) { return excluded; } var selector = FilterType == XUnitFilterType.Trait ? $"'{SelectorName}':'{SelectorValue}'" : $"'{SelectorValue}'"; log($"[FILTER] {(excluded ? excludedText : includedText)} test (filtered by {FilterType}; {selector}): {testCase.DisplayName}"); return excluded; } private static bool ReportFilteredAssembly(TestAssemblyInfo assemblyInfo, bool excluded, Action? log = null) { if (log == null) { return excluded; } const string includedPrefix = "Included"; const string excludedPrefix = "Excluded"; log($"[FILTER] {(excluded ? excludedPrefix : includedPrefix)} assembly: {assemblyInfo.FullPath}"); return excluded; } private static void AppendDesc(StringBuilder sb, string name, string? value) { if (string.IsNullOrEmpty(value)) { return; } sb.Append($"; {name}: {value}"); } public override string ToString() { var sb = new StringBuilder("XUnitFilter ["); sb.Append($"Type: {FilterType}; "); sb.Append(Exclude ? "exclude" : "include"); if (!string.IsNullOrEmpty(AssemblyName)) { sb.Append($"; AssemblyName: {AssemblyName}"); } switch (FilterType) { case XUnitFilterType.Assembly: break; case XUnitFilterType.Namespace: AppendDesc(sb, "Namespace", SelectorValue); break; case XUnitFilterType.Single: AppendDesc(sb, "Method", SelectorValue); break; case XUnitFilterType.Trait: AppendDesc(sb, "Trait name", SelectorName); AppendDesc(sb, "Trait value", SelectorValue); break; case XUnitFilterType.TypeName: AppendDesc(sb, "Class", SelectorValue); break; default: sb.Append("; Unknown filter type"); break; } sb.Append(']'); return sb.ToString(); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/XUnitFilterType.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; internal enum XUnitFilterType { Trait, TypeName, Assembly, Single, Namespace, } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/XUnitFiltersCollection.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using Microsoft.DotNet.XHarness.TestRunners.Common; using Xunit.Abstractions; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; /// /// Class that contains a collection of filters and can be used to decide if a test should be executed or not. /// internal class XUnitFiltersCollection : List { /// /// Return all the filters that are applied to assemblies. /// public IEnumerable AssemblyFilters => Enumerable.Where(this, f => f.FilterType == XUnitFilterType.Assembly); /// /// Return all the filters that are applied to test cases. /// public IEnumerable TestCaseFilters => Enumerable.Where(this, f => f.FilterType != XUnitFilterType.Assembly); // loop over all the filters, if we have conflicting filters, that is, one exclude and other one // includes, we will always include since it is better to run a test thant to skip it and think // you ran in. private bool IsExcludedInternal(IEnumerable filters, Func isExcludedCb) { // No filters : include by default // Any exclude filters : include by default // Only include filters : exclude by default var isExcluded = filters.Any() && filters.All(f => !f.Exclude); foreach (var filter in filters) { var doesExclude = isExcludedCb(filter); if (filter.Exclude) { isExcluded |= doesExclude; } else { // filter does not exclude, that means that if it include, we should include and break the // loop, always include if (!doesExclude) { return false; } } } return isExcluded; } public bool IsExcluded(TestAssemblyInfo assembly, Action? log = null) => IsExcludedInternal(AssemblyFilters, f => f.IsExcluded(assembly, log)); public bool IsExcluded(ITestCase testCase, Action? log = null) { // Check each type of filter separately. For conflicts within a type of filter, we want the inclusion // (the logic in IsExcludedInternal), but if all filters for a filter type exclude a test case, we want // the exclusion. For example, if a test class is included, but it contains tests that have excluded // traits, the behaviour should be to run all tests in that class without the excluded traits. foreach (IGrouping filterGroup in TestCaseFilters.GroupBy(f => f.FilterType)) { if (IsExcludedInternal(filterGroup, f => f.IsExcluded(testCase, log))) { return true; } } return false; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/XUnitTestRunner.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using System.Xml.Xsl; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.TestRunners.Common; using Xunit; using Xunit.Abstractions; namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; internal class XsltIdGenerator { // NUnit3 xml does not have schema, there is no much info about it, most examples just have incremental IDs. private int _seed = 1000; public int GenerateHash() => _seed++; } internal class XUnitTestRunner : XunitTestRunnerBase { private readonly TestMessageSink _messageSink; public int? MaxParallelThreads { get; set; } private XElement _assembliesElement; internal XElement ConsumeAssembliesElement() { Debug.Assert(_assembliesElement != null, "ConsumeAssembliesElement called before Run() or after ConsumeAssembliesElement() was already called."); var res = _assembliesElement; _assembliesElement = null; FailureInfos.Clear(); return res; } public AppDomainSupport AppDomainSupport { get; set; } = AppDomainSupport.Denied; protected override string ResultsFileName { get; set; } = "TestResults.xUnit.xml"; protected string TestStagePrefix { get; init; } = "\t"; public XUnitTestRunner(LogWriter logger) : base(logger) { _messageSink = new TestMessageSink(); _messageSink.Diagnostics.DiagnosticMessageEvent += HandleDiagnosticMessage; _messageSink.Diagnostics.ErrorMessageEvent += HandleDiagnosticErrorMessage; _messageSink.Discovery.DiscoveryCompleteMessageEvent += HandleDiscoveryCompleteMessage; _messageSink.Discovery.TestCaseDiscoveryMessageEvent += HandleDiscoveryTestCaseMessage; _messageSink.Runner.TestAssemblyDiscoveryFinishedEvent += HandleTestAssemblyDiscoveryFinished; _messageSink.Runner.TestAssemblyDiscoveryStartingEvent += HandleTestAssemblyDiscoveryStarting; _messageSink.Runner.TestAssemblyExecutionFinishedEvent += HandleTestAssemblyExecutionFinished; _messageSink.Runner.TestAssemblyExecutionStartingEvent += HandleTestAssemblyExecutionStarting; _messageSink.Runner.TestExecutionSummaryEvent += HandleTestExecutionSummary; _messageSink.Execution.AfterTestFinishedEvent += (MessageHandlerArgs args) => HandleEvent("AfterTestFinishedEvent", args, HandleAfterTestFinished); _messageSink.Execution.AfterTestStartingEvent += (MessageHandlerArgs args) => HandleEvent("AfterTestStartingEvent", args, HandleAfterTestStarting); _messageSink.Execution.BeforeTestFinishedEvent += (MessageHandlerArgs args) => HandleEvent("BeforeTestFinishedEvent", args, HandleBeforeTestFinished); _messageSink.Execution.BeforeTestStartingEvent += (MessageHandlerArgs args) => HandleEvent("BeforeTestStartingEvent", args, HandleBeforeTestStarting); _messageSink.Execution.TestAssemblyCleanupFailureEvent += (MessageHandlerArgs args) => HandleEvent("TestAssemblyCleanupFailureEvent", args, HandleTestAssemblyCleanupFailure); _messageSink.Execution.TestAssemblyFinishedEvent += (MessageHandlerArgs args) => HandleEvent("TestAssemblyFinishedEvent", args, HandleTestAssemblyFinished); _messageSink.Execution.TestAssemblyStartingEvent += (MessageHandlerArgs args) => HandleEvent("TestAssemblyStartingEvent", args, HandleTestAssemblyStarting); _messageSink.Execution.TestCaseCleanupFailureEvent += (MessageHandlerArgs args) => HandleEvent("TestCaseCleanupFailureEvent", args, HandleTestCaseCleanupFailure); _messageSink.Execution.TestCaseFinishedEvent += (MessageHandlerArgs args) => HandleEvent("TestCaseFinishedEvent", args, HandleTestCaseFinished); _messageSink.Execution.TestCaseStartingEvent += (MessageHandlerArgs args) => HandleEvent("TestStartingEvent", args, HandleTestCaseStarting); _messageSink.Execution.TestClassCleanupFailureEvent += (MessageHandlerArgs args) => HandleEvent("TestClassCleanupFailureEvent", args, HandleTestClassCleanupFailure); _messageSink.Execution.TestClassConstructionFinishedEvent += (MessageHandlerArgs args) => HandleEvent("TestClassConstructionFinishedEvent", args, HandleTestClassConstructionFinished); _messageSink.Execution.TestClassConstructionStartingEvent += (MessageHandlerArgs args) => HandleEvent("TestClassConstructionStartingEvent", args, HandleTestClassConstructionStarting); _messageSink.Execution.TestClassDisposeFinishedEvent += (MessageHandlerArgs args) => HandleEvent("TestClassDisposeFinishedEvent", args, HandleTestClassDisposeFinished); _messageSink.Execution.TestClassDisposeStartingEvent += (MessageHandlerArgs args) => HandleEvent("TestClassDisposeStartingEvent", args, HandleTestClassDisposeStarting); _messageSink.Execution.TestClassFinishedEvent += (MessageHandlerArgs args) => HandleEvent("TestClassFinishedEvent", args, HandleTestClassFinished); _messageSink.Execution.TestClassStartingEvent += (MessageHandlerArgs args) => HandleEvent("TestClassStartingEvent", args, HandleTestClassStarting); _messageSink.Execution.TestCleanupFailureEvent += (MessageHandlerArgs args) => HandleEvent("TestCleanupFailureEvent", args, HandleTestCleanupFailure); _messageSink.Execution.TestCollectionCleanupFailureEvent += (MessageHandlerArgs args) => HandleEvent("TestCollectionCleanupFailureEvent", args, HandleTestCollectionCleanupFailure); _messageSink.Execution.TestCollectionFinishedEvent += (MessageHandlerArgs args) => HandleEvent("TestCollectionFinishedEvent", args, HandleTestCollectionFinished); _messageSink.Execution.TestCollectionStartingEvent += (MessageHandlerArgs args) => HandleEvent("TestCollectionStartingEvent", args, HandleTestCollectionStarting); _messageSink.Execution.TestFailedEvent += (MessageHandlerArgs args) => HandleEvent("TestFailedEvent", args, HandleTestFailed); _messageSink.Execution.TestFinishedEvent += (MessageHandlerArgs args) => HandleEvent("TestFinishedEvent", args, HandleTestFinished); _messageSink.Execution.TestMethodCleanupFailureEvent += (MessageHandlerArgs args) => HandleEvent("TestMethodCleanupFailureEvent", args, HandleTestMethodCleanupFailure); _messageSink.Execution.TestMethodFinishedEvent += (MessageHandlerArgs args) => HandleEvent("TestMethodFinishedEvent", args, HandleTestMethodFinished); _messageSink.Execution.TestMethodStartingEvent += (MessageHandlerArgs args) => HandleEvent("TestMethodStartingEvent", args, HandleTestMethodStarting); _messageSink.Execution.TestOutputEvent += (MessageHandlerArgs args) => HandleEvent("TestOutputEvent", args, HandleTestOutput); _messageSink.Execution.TestPassedEvent += (MessageHandlerArgs args) => HandleEvent("TestPassedEvent", args, HandleTestPassed); _messageSink.Execution.TestSkippedEvent += (MessageHandlerArgs args) => HandleEvent("TestSkippedEvent", args, HandleTestSkipped); _messageSink.Execution.TestStartingEvent += (MessageHandlerArgs args) => HandleEvent("TestStartingEvent", args, HandleTestStarting); } public void AddFilter(XUnitFilter filter) { if (filter != null) { _filters.Add(filter); } } public void SetFilters(List newFilters) { if (newFilters == null) { _filters = null; return; } if (_filters == null) { _filters = new XUnitFiltersCollection(); } _filters.AddRange(newFilters); } private void HandleEvent(string name, MessageHandlerArgs args, Action> actualHandler) where T : class, IMessageSinkMessage { try { actualHandler(args); } catch (Exception ex) { OnError($"Handler for event {name} failed with exception"); OnError(ex.ToString()); } } protected string GetThreadIdForLog() { if (EnvironmentVariables.IsLogThreadId()) return $"[{Thread.CurrentThread.ManagedThreadId}]"; return string.Empty; } private void HandleTestStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } if (EnvironmentVariables.IsLogTestStart()) { OnInfo($"{TestStagePrefix}[STRT]{GetThreadIdForLog()} {args.Message.Test.DisplayName}"); } OnDebug("Test starting"); LogTestDetails(args.Message.Test, log: OnDebug); ReportTestCases(" Associated", args.Message.TestCases, args.Message.TestCase, OnDiagnostic); } private void HandleTestSkipped(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } RaiseTestSkippedCase(args.Message, args.Message.TestCases, args.Message.TestCase); } private void HandleTestPassed(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } PassedTests++; OnInfo($"{TestStagePrefix}[PASS]{GetThreadIdForLog()} {args.Message.TestCase.DisplayName}"); LogTestDetails(args.Message.Test, log: OnDebug); LogTestOutput(args.Message, log: OnDiagnostic); ReportTestCases(" Associated", args.Message.TestCases, log: OnDiagnostic); // notify the completion of the test OnTestCompleted(( TestName: args.Message.Test.DisplayName, TestResult: TestResult.Passed )); } private void HandleTestOutput(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDebug(args.Message.Output); } private void HandleTestMethodStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDebug("Test method starting"); LogTestMethodDetails(args.Message.TestMethod.Method, log: OnDebug); LogTestClassDetails(args.Message.TestMethod.TestClass, log: OnDebug); ReportTestCases(" Associated", args.Message.TestCases, log: OnDiagnostic); } private void HandleTestMethodFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDebug("Test method finished"); LogTestMethodDetails(args.Message.TestMethod.Method, log: OnDebug); LogTestClassDetails(args.Message.TestMethod.TestClass, log: OnDebug); LogSummary(args.Message, log: OnDebug); ReportTestCases(" Associated", args.Message.TestCases, log: OnDiagnostic); } private void HandleTestMethodCleanupFailure(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnError($"Test method cleanup failure{GetAssemblyInfo(args.Message.TestAssembly)}"); LogTestMethodDetails(args.Message.TestMethod.Method, log: OnError); LogTestClassDetails(args.Message.TestMethod.TestClass, log: OnError); ReportTestCases(" Associated", args.Message.TestCases, log: OnDiagnostic); LogFailureInformation(args.Message, log: OnError); } private void HandleTestFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } ExecutedTests++; OnDiagnostic("Test finished"); LogTestDetails(args.Message.Test, log: OnDiagnostic); LogTestOutput(args.Message, log: OnDiagnostic); ReportTestCases(" Associated", args.Message.TestCases, args.Message.TestCase, OnDiagnostic); } private void HandleTestFailed(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } FailedTests++; string assemblyInfo = GetAssemblyInfo(args.Message.TestAssembly); var sb = new StringBuilder($"{TestStagePrefix}[FAIL]{GetThreadIdForLog()} {args.Message.TestCase.DisplayName}"); LogTestDetails(args.Message.Test, OnDebug, sb); sb.AppendLine(); if (!string.IsNullOrEmpty(assemblyInfo)) { sb.AppendLine($" Assembly: {assemblyInfo}"); } LogSourceInformation(args.Message.TestCase.SourceInformation, OnDebug, sb); LogFailureInformation(args.Message, OnDebug, sb); sb.AppendLine(); LogTestOutput(args.Message, OnDebug, sb); sb.AppendLine(); if (args.Message.TestCase.Traits != null && args.Message.TestCase.Traits.Count > 0) { foreach (var kvp in args.Message.TestCase.Traits) { string message = $" Test trait name: {kvp.Key}"; OnDebug(message); sb.AppendLine(message); foreach (string v in kvp.Value) { message = $" value: {v}"; OnDebug(message); sb.AppendLine(message); } } sb.AppendLine(); } ReportTestCases(" Associated", args.Message.TestCases, args.Message.TestCase, OnDiagnostic); FailureInfos.Add(new TestFailureInfo { TestName = args.Message.Test?.DisplayName, Message = sb.ToString() }); OnError($"{TestStagePrefix}[FAIL]{GetThreadIdForLog()} {args.Message.Test.DisplayName}{Environment.NewLine}{ExceptionUtility.CombineMessages(args.Message)}{Environment.NewLine}{ExceptionUtility.CombineStackTraces(args.Message)}"); OnDebug(sb.ToString()); OnTestCompleted(( TestName: args.Message.Test.DisplayName, TestResult: TestResult.Failed )); } private void HandleTestCollectionStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnInfo($"\n{args.Message.TestCollection.DisplayName}"); OnDebug("Test collection starting"); LogTestCollectionDetails(args.Message.TestCollection, log: OnDebug); ReportTestCases(" Associated", args.Message.TestCases, log: OnDiagnostic); } private void HandleTestCollectionFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDebug("Test collection finished"); LogSummary(args.Message, log: OnDebug); LogTestCollectionDetails(args.Message.TestCollection, log: OnDebug); ReportTestCases(" Associated", args.Message.TestCases, log: OnDiagnostic); } private void HandleTestCollectionCleanupFailure(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnError("Error during test collection cleanup"); LogTestCollectionDetails(args.Message.TestCollection, log: OnError); ReportTestCases(" Associated", args.Message.TestCases, log: OnError); LogFailureInformation(args.Message, log: OnError); } private void HandleTestCleanupFailure(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnError($"Test cleanup failure{GetAssemblyInfo(args.Message.TestAssembly)}"); LogTestDetails(args.Message.Test, log: OnError); ReportTestCases(" Associated", args.Message.TestCases, log: OnError); LogFailureInformation(args.Message, log: OnError); } private void HandleTestClassStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDiagnostic("Test class starting"); LogTestClassDetails(args.Message.TestClass, log: OnDiagnostic); } private void HandleTestClassFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDebug("Test class finished"); OnInfo($"{args.Message.TestClass.Class.Name} {args.Message.ExecutionTime} ms"); LogTestClassDetails(args.Message.TestClass, OnDebug); ReportTestCases(" Associated", args.Message.TestCases, log: OnDiagnostic); } private void HandleTestClassDisposeStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDiagnostic("Test class dispose starting"); LogTestDetails(args.Message.Test, log: OnDiagnostic); ReportTestCases(" Associated", args.Message.TestCases, log: OnDiagnostic); } private void HandleTestClassDisposeFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDiagnostic("Test class dispose finished"); LogTestDetails(args.Message.Test, log: OnDiagnostic); ReportTestCases(" Associated", args.Message.TestCases, log: OnDiagnostic); } private void HandleTestClassConstructionStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDiagnostic("Test class construction starting"); LogTestDetails(args.Message.Test, OnDiagnostic); ReportTestCases(" Associated", args.Message.TestCases, args.Message.TestCase, OnDiagnostic); } private void HandleTestClassConstructionFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDiagnostic("Test class construction finished"); LogTestDetails(args.Message.Test, log: OnDiagnostic); ReportTestCases(" Associated", args.Message.TestCases, args.Message.TestCase, OnDiagnostic); } private void HandleTestClassCleanupFailure(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnError($"Test class cleanup error{GetAssemblyInfo(args.Message.TestAssembly)}"); LogTestClassDetails(args.Message.TestClass, log: OnError); LogTestCollectionDetails(args.Message.TestCollection, log: OnError); ReportTestCases(" Associated", args.Message.TestCases, log: OnError); LogFailureInformation(args.Message, log: OnError); } private void HandleTestCaseStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDiagnostic("Test case starting"); ReportTestCase(" Starting", args.Message.TestCase, log: OnDiagnostic); ReportTestCases(" Associated", args.Message.TestCases, args.Message.TestCase, OnDiagnostic); } private void HandleTestCaseFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDebug("Test case finished executing"); ReportTestCase(" Finished", args.Message.TestCase, log: OnDebug); ReportTestCases(" Associated", args.Message.TestCases, args.Message.TestCase, OnDebug); LogSummary(args.Message, log: OnDebug); } private void HandleTestCaseCleanupFailure(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnError("Test case cleanup failure"); ReportTestCase(" Failed", args.Message.TestCase, log: OnError); ReportTestCases(" Associated", args.Message.TestCases, args.Message.TestCase, OnError); LogFailureInformation(args.Message, log: OnError); } private void HandleTestAssemblyStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnInfo($"[Test environment: {args.Message.TestEnvironment}]"); OnInfo($"[Test framework: {args.Message.TestFrameworkDisplayName}]"); LogAssemblyInformation(args.Message, log: OnDebug); ReportTestCases(" Associated", args.Message.TestCases, log: OnDebug); } private void HandleTestAssemblyFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } TotalTests = args.Message.TestsRun; // HACK: We are not counting correctly all the tests OnDebug("Execution process for assembly finished"); LogAssemblyInformation(args.Message, log: OnDebug); LogSummary(args.Message, log: OnDebug); ReportTestCases(" Associated", args.Message.TestCases, log: OnDiagnostic); } private void HandleTestAssemblyCleanupFailure(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnError("Assembly cleanup failure"); LogAssemblyInformation(args.Message, OnError); ReportTestCases(" Associated", args.Message.TestCases, log: OnError); LogFailureInformation(args.Message, log: OnError); } private void HandleBeforeTestStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } // notify that a method is starting OnTestStarted(args.Message.Test.DisplayName); OnDiagnostic($"'Before' method for test '{args.Message.Test.DisplayName}' starting"); } private void HandleBeforeTestFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDiagnostic($"'Before' method for test '{args.Message.Test.DisplayName}' finished"); } private void HandleAfterTestStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDiagnostic($"'After' method for test '{args.Message.Test.DisplayName}' starting"); } private void HandleAfterTestFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDiagnostic($"'After' method for test '{args.Message.Test.DisplayName}' finished"); } private void HandleTestExecutionSummary(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnInfo("All tests finished"); OnInfo($" Elapsed time: {args.Message.ElapsedClockTime}"); if (args.Message.Summaries == null || args.Message.Summaries.Count == 0) { return; } foreach (KeyValuePair summary in args.Message.Summaries) { OnInfo(string.Empty); OnInfo($" Assembly: {summary.Key}"); LogSummary(summary.Value, log: OnDebug); } } private void HandleTestAssemblyExecutionStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnInfo($"Execution starting for assembly {args.Message.Assembly.AssemblyFilename}"); } private void HandleTestAssemblyExecutionFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnInfo($"Execution finished for assembly {args.Message.Assembly.AssemblyFilename}"); LogSummary(args.Message.ExecutionSummary, log: OnDebug); } private void HandleTestAssemblyDiscoveryStarting(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnInfo($"Discovery for assembly {args.Message.Assembly.AssemblyFilename} starting"); OnInfo($" Will use AppDomain: {args.Message.AppDomain.YesNo()}"); } private void HandleTestAssemblyDiscoveryFinished(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnInfo($"Discovery for assembly {args.Message.Assembly.AssemblyFilename} finished"); OnInfo($" Test cases discovered: {args.Message.TestCasesDiscovered}"); OnInfo($" Test cases to run: {args.Message.TestCasesToRun}"); } private void HandleDiagnosticMessage(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnDiagnostic(args.Message.Message); } private void HandleDiagnosticErrorMessage(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } LogFailureInformation(args.Message); } private void HandleDiscoveryCompleteMessage(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } OnInfo("Discovery complete"); } private void HandleDiscoveryTestCaseMessage(MessageHandlerArgs args) { if (args == null || args.Message == null) { return; } ITestCase singleTestCase = args.Message.TestCase; ReportTestCases("Discovered", args.Message.TestCases, log: OnInfo, ignore: (ITestCase tc) => tc == singleTestCase); ReportTestCase("Discovered", singleTestCase, log: OnInfo); } private void RaiseTestSkippedCase(ITestResultMessage message, IEnumerable testCases, ITestCase testCase) { SkippedTests++; OnInfo($"{TestStagePrefix}[IGNORED] {testCase.DisplayName}"); LogTestDetails(message.Test, log: OnDebug); LogTestOutput(message, log: OnDiagnostic); ReportTestCases(" Associated", testCases, log: OnDiagnostic); // notify that the test completed because it was skipped OnTestCompleted(( TestName: message.Test.DisplayName, TestResult: TestResult.Skipped )); } private void ReportTestCases(string verb, IEnumerable testCases, ITestCase ignoreTestCase, Action log = null) => ReportTestCases(verb, testCases, log, (ITestCase tc) => ignoreTestCase == tc); private void ReportTestCases(string verb, IEnumerable testCases, Action log = null, Func ignore = null) { if (testCases == null) { return; } foreach (ITestCase tc in testCases) { if (ignore != null && ignore(tc)) { continue; } ReportTestCase(verb, tc, log); } } private void ReportTestCase(string verb, ITestCase testCase, Action log = null) { if (testCase == null) { return; } EnsureLogger(log)($"{verb} test case: {testCase.DisplayName}"); } private void LogAssemblyInformation(ITestAssemblyMessage message, Action log = null, StringBuilder sb = null) { if (message == null) { return; } do_log($"[Assembly name: {message.TestAssembly.Assembly.Name}]", log, sb); do_log($"[Assembly path: {message.TestAssembly.Assembly.AssemblyPath}]", OnDiagnostic, sb); } private void LogFailureInformation(IFailureInformation info, Action log = null, StringBuilder sb = null) { if (info == null) { return; } string message = ExceptionUtility.CombineMessages(info); do_log($" Exception messages: {message}", log, sb); string traces = ExceptionUtility.CombineStackTraces(info); do_log($" Exception stack traces: {traces}", log, sb); } private Action EnsureLogger(Action log) => log ?? OnInfo; #pragma warning disable IDE0060 // Remove unused parameter private static void LogTestMethodDetails(IMethodInfo method, Action log = null, StringBuilder sb = null) #pragma warning restore IDE0060 // Remove unused parameter { // log = EnsureLogger(log); // log ($" Test method name: {method.Type.Name}.{method.Name}"); } private void LogTestOutput(ITestFinished test, Action log = null, StringBuilder sb = null) => LogTestOutput(test.ExecutionTime, test.Output, log, sb); private void LogTestOutput(ITestResultMessage test, Action log = null, StringBuilder sb = null) => LogTestOutput(test.ExecutionTime, test.Output, log, sb); private void LogTestOutput(decimal executionTime, string output, Action log = null, StringBuilder sb = null) { do_log($" Execution time: {executionTime}", log, sb); if (!string.IsNullOrEmpty(output)) { do_log(" **** Output start ****", log, sb); foreach (string line in output.Split('\n')) { do_log(line, log, sb); } do_log(" **** Output end ****", log, sb); } } private void LogTestCollectionDetails(ITestCollection collection, Action log = null, StringBuilder sb = null) => do_log($" Test collection: {collection.DisplayName}", log, sb); private void LogTestClassDetails(ITestClass klass, Action log = null, StringBuilder sb = null) { do_log($" Class name: {klass.Class.Name}", log, sb); do_log($" Class assembly: {klass.Class.Assembly.Name}", OnDebug, sb); do_log($" Class assembly path: {klass.Class.Assembly.AssemblyPath}", OnDebug, sb); } private void LogTestDetails(ITest test, Action log = null, StringBuilder sb = null) { do_log($" Test name: {test.DisplayName}", log, sb); if (string.Compare(test.DisplayName, test.TestCase.DisplayName, StringComparison.Ordinal) != 0) { do_log($" Test case: {test.TestCase.DisplayName}", log, sb); } } private void LogSummary(IFinishedMessage summary, Action log = null, StringBuilder sb = null) { do_log($" Time: {summary.ExecutionTime}", log, sb); do_log($" Total tests run: {summary.TestsRun}", log, sb); do_log($" Skipped tests: {summary.TestsSkipped}", log, sb); do_log($" Failed tests: {summary.TestsFailed}", log, sb); } private void LogSummary(ExecutionSummary summary, Action log = null, StringBuilder sb = null) { do_log($" Time: {summary.Time}", log, sb); do_log($" Total tests run: {summary.Total}", log, sb); do_log($" Total errors: {summary.Errors}", log, sb); do_log($" Skipped tests: {summary.Skipped}", log, sb); do_log($" Failed tests: {summary.Failed}", log, sb); } private void LogSourceInformation(ISourceInformation source, Action log = null, StringBuilder sb = null) { if (source == null || string.IsNullOrEmpty(source.FileName)) { return; } string location = source.FileName; if (source.LineNumber != null && source.LineNumber >= 0) { location += $":{source.LineNumber}"; } do_log($" Source: {location}", log, sb); sb?.AppendLine(); } private static string GetAssemblyInfo(ITestAssembly assembly) { string name = assembly?.Assembly?.Name?.Trim(); if (string.IsNullOrEmpty(name)) { return name; } return $" [{name}]"; } private void do_log(string message, Action log = null, StringBuilder sb = null) { log = EnsureLogger(log); if (sb != null) { sb.Append(message); } log(message); } public override async Task Run(IEnumerable testAssemblies) { if (testAssemblies == null) { throw new ArgumentNullException(nameof(testAssemblies)); } if (_filters != null && _filters.Count > 0) { do_log("Configured filters:"); foreach (XUnitFilter filter in _filters) { do_log($" {filter}"); } } _assembliesElement = new XElement("assemblies"); Action log = LogExcludedTests ? (s) => do_log(s) : (Action)null; foreach (TestAssemblyInfo assemblyInfo in testAssemblies) { if (assemblyInfo == null || assemblyInfo.Assembly == null) { continue; } if (_filters.AssemblyFilters.Any() && _filters.IsExcluded(assemblyInfo, log)) { continue; } if (string.IsNullOrEmpty(assemblyInfo.FullPath)) { OnWarning($"Assembly '{assemblyInfo.Assembly}' cannot be found on the filesystem. xUnit requires access to actual on-disk file."); continue; } OnInfo($"Assembly: {assemblyInfo.Assembly} ({assemblyInfo.FullPath})"); XElement assemblyElement = null; try { OnAssemblyStart(assemblyInfo.Assembly); assemblyElement = await Run(assemblyInfo.Assembly, assemblyInfo.FullPath).ConfigureAwait(false); } catch (FileNotFoundException ex) { OnWarning($"Assembly '{assemblyInfo.Assembly}' using path '{assemblyInfo.FullPath}' cannot be found on the filesystem. xUnit requires access to actual on-disk file."); OnWarning($"Exception is '{ex}'"); } finally { OnAssemblyFinish(assemblyInfo.Assembly); if (assemblyElement != null) { _assembliesElement.Add(assemblyElement); } } } LogFailureSummary(); TotalTests += FilteredTests; // ensure that we do have in the total run the excluded ones. } public override Task WriteResultsToFile(XmlResultJargon jargon) { if (_assembliesElement == null) { return Task.FromResult(string.Empty); } // remove all the empty nodes _assembliesElement.Descendants().Where(e => e.Name == "collection" && !e.Descendants().Any()).Remove(); string outputFilePath = GetResultsFilePath(); var settings = new XmlWriterSettings { Indent = true }; using (var xmlWriter = XmlWriter.Create(outputFilePath, settings)) { switch (jargon) { case XmlResultJargon.TouchUnit: case XmlResultJargon.NUnitV2: Transform_Results("NUnitXml.xslt", _assembliesElement, xmlWriter); break; case XmlResultJargon.NUnitV3: Transform_Results("NUnit3Xml.xslt", _assembliesElement, xmlWriter); break; default: // xunit as default, includes when we got Missing _assembliesElement.Save(xmlWriter); break; } } return Task.FromResult(outputFilePath); } public override Task WriteResultsToFile(TextWriter writer, XmlResultJargon jargon) { if (_assembliesElement == null) { return Task.CompletedTask; } // remove all the empty nodes _assembliesElement.Descendants().Where(e => e.Name == "collection" && !e.Descendants().Any()).Remove(); var settings = new XmlWriterSettings { Indent = true }; using (var xmlWriter = XmlWriter.Create(writer, settings)) { switch (jargon) { case XmlResultJargon.TouchUnit: case XmlResultJargon.NUnitV2: try { Transform_Results("NUnitXml.xslt", _assembliesElement, xmlWriter); } catch (Exception e) { writer.WriteLine(e); } break; case XmlResultJargon.NUnitV3: try { Transform_Results("NUnit3Xml.xslt", _assembliesElement, xmlWriter); } catch (Exception e) { writer.WriteLine(e); } break; default: // xunit as default, includes when we got Missing _assembliesElement.Save(xmlWriter); break; } } return Task.CompletedTask; } private void Transform_Results(string xsltResourceName, XElement element, XmlWriter writer) { var xmlTransform = new System.Xml.Xsl.XslCompiledTransform(); var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith(xsltResourceName, StringComparison.Ordinal)).FirstOrDefault(); if (name == null) { return; } using (var xsltStream = GetType().Assembly.GetManifestResourceStream(name)) { if (xsltStream == null) { throw new Exception($"Stream with name {name} cannot be found! We have {GetType().Assembly.GetManifestResourceNames()[0]}"); } // add the extension so that we can get the hash from the name of the test // Create an XsltArgumentList. var xslArg = new XsltArgumentList(); var generator = new XsltIdGenerator(); xslArg.AddExtensionObject("urn:hash-generator", generator); using (var xsltReader = XmlReader.Create(xsltStream)) using (var xmlReader = element.CreateReader()) { xmlTransform.Load(xsltReader); xmlTransform.Transform(xmlReader, xslArg, writer); } } } protected virtual Stream GetConfigurationFileStream(Assembly assembly) { if (assembly == null) { throw new ArgumentNullException(nameof(assembly)); } string path = assembly.Location?.Trim(); if (string.IsNullOrEmpty(path)) { return null; } path = Path.Combine(path, ".xunit.runner.json"); if (!File.Exists(path)) { return null; } return File.OpenRead(path); } protected virtual TestAssemblyConfiguration GetConfiguration(Assembly assembly) { if (assembly == null) { throw new ArgumentNullException(nameof(assembly)); } Stream configStream = GetConfigurationFileStream(assembly); if (configStream != null) { using (configStream) { return ConfigReader.Load(configStream); } } return null; } protected virtual ITestFrameworkDiscoveryOptions GetFrameworkOptionsForDiscovery(TestAssemblyConfiguration configuration) { if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } return TestFrameworkOptions.ForDiscovery(configuration); } protected virtual ITestFrameworkExecutionOptions GetFrameworkOptionsForExecution(TestAssemblyConfiguration configuration) { if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } return TestFrameworkOptions.ForExecution(configuration); } private async Task Run(Assembly assembly, string assemblyPath) { using (var frontController = new XunitFrontController(AppDomainSupport, assemblyPath, null, false)) { using (var discoverySink = new TestDiscoverySink()) { var configuration = GetConfiguration(assembly) ?? new TestAssemblyConfiguration() { PreEnumerateTheories = false }; ITestFrameworkDiscoveryOptions discoveryOptions = GetFrameworkOptionsForDiscovery(configuration); discoveryOptions.SetSynchronousMessageReporting(true); Logger.OnDebug($"Starting test discovery in the '{assembly}' assembly"); frontController.Find(false, discoverySink, discoveryOptions); Logger.OnDebug($"Test discovery in assembly '{assembly}' completed"); discoverySink.Finished.WaitOne(); if (discoverySink.TestCases == null || discoverySink.TestCases.Count == 0) { Logger.Info("No test cases discovered"); return null; } TotalTests += discoverySink.TestCases.Count; List testCases; if (_filters != null && _filters.TestCaseFilters.Any()) { Action log = LogExcludedTests ? (s) => do_log(s) : (Action)null; testCases = discoverySink.TestCases.Where( tc => !_filters.IsExcluded(tc, log)).ToList(); FilteredTests += discoverySink.TestCases.Count - testCases.Count; } else { testCases = discoverySink.TestCases; } var summaryTaskSource = new TaskCompletionSource(); var resultsXmlAssembly = new XElement("assembly"); #pragma warning disable CS0618 // Delegating*Sink types are marked obsolete, but we can't move to ExecutionSink yet: https://github.com/dotnet/arcade/issues/14375 var resultsSink = new DelegatingXmlCreationSink(new DelegatingExecutionSummarySink(_messageSink), resultsXmlAssembly); #pragma warning restore var completionSink = new CompletionCallbackExecutionSink(resultsSink, summary => summaryTaskSource.SetResult(summary)); ITestFrameworkExecutionOptions executionOptions = GetFrameworkOptionsForExecution(configuration); executionOptions.SetDisableParallelization(!RunInParallel); executionOptions.SetSynchronousMessageReporting(true); executionOptions.SetMaxParallelThreads(MaxParallelThreads); frontController.RunTests(testCases, completionSink, executionOptions); await summaryTaskSource.Task.ConfigureAwait(false); return resultsXmlAssembly; } } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/XunitTestRunnerBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using Microsoft.DotNet.XHarness.TestRunners.Common; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; public abstract class XunitTestRunnerBase : TestRunner { private protected XUnitFiltersCollection _filters = new(); protected XunitTestRunnerBase(LogWriter logger) : base(logger) { } public override void SkipTests(IEnumerable tests) { if (tests.Any()) { // create a single filter per test foreach (var t in tests) { if (t.StartsWith("KLASS:", StringComparison.Ordinal)) { var klass = t.Replace("KLASS:", ""); _filters.Add(XUnitFilter.CreateClassFilter(klass, true)); } else if (t.StartsWith("KLASS32:", StringComparison.Ordinal) && IntPtr.Size == 4) { var klass = t.Replace("KLASS32:", ""); _filters.Add(XUnitFilter.CreateClassFilter(klass, true)); } else if (t.StartsWith("KLASS64:", StringComparison.Ordinal) && IntPtr.Size == 8) { var klass = t.Replace("KLASS32:", ""); _filters.Add(XUnitFilter.CreateClassFilter(klass, true)); } else if (t.StartsWith("Platform32:", StringComparison.Ordinal) && IntPtr.Size == 4) { var filter = t.Replace("Platform32:", ""); _filters.Add(XUnitFilter.CreateSingleFilter(filter, true)); } else { _filters.Add(XUnitFilter.CreateSingleFilter(t, true)); } } } } public override void SkipCategories(IEnumerable categories) => SkipCategories(categories, isExcluded: true); public virtual void SkipCategories(IEnumerable categories, bool isExcluded) { if (categories == null) { throw new ArgumentNullException(nameof(categories)); } foreach (var c in categories) { var traitInfo = c.Split('='); if (traitInfo.Length == 2) { _filters.Add(XUnitFilter.CreateTraitFilter(traitInfo[0], traitInfo[1], isExcluded)); } else { _filters.Add(XUnitFilter.CreateTraitFilter(c, null, isExcluded)); } } } public override void SkipMethod(string method, bool isExcluded) => _filters.Add(XUnitFilter.CreateSingleFilter(singleTestName: method, exclude: isExcluded)); public override void SkipClass(string className, bool isExcluded) => _filters.Add(XUnitFilter.CreateClassFilter(className: className, exclude: isExcluded)); public virtual void SkipNamespace(string namespaceName, bool isExcluded) => _filters.Add(XUnitFilter.CreateNamespaceFilter(namespaceName, exclude: isExcluded)); } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/YieldingXunitTestFrameworkExecutor.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; public class YieldingXunit2 : Xunit2 { readonly YieldingXunitTestFrameworkExecutor remoteExecutor; public YieldingXunit2(AppDomainSupport appDomainSupport, ISourceInformationProvider sourceInformationProvider, string assemblyFileName, string configFileName = null, bool shadowCopy = true, string shadowCopyFolder = null, IMessageSink diagnosticMessageSink = null, bool verifyTestAssemblyExists = true) : base(appDomainSupport, sourceInformationProvider, assemblyFileName, configFileName, shadowCopy, shadowCopyFolder, diagnosticMessageSink, verifyTestAssemblyExists) { var an = Assembly.Load(new AssemblyName { Name = Path.GetFileNameWithoutExtension(assemblyFileName) }).GetName(); var assemblyName = new AssemblyName { Name = an.Name, Version = an.Version }; //remoteExecutor = Framework.GetExecutor(assemblyName); remoteExecutor = new YieldingXunitTestFrameworkExecutor(assemblyName, NullSourceInformationProvider.Instance, DiagnosticMessageSink); } public async Task RunTestsAsync(IEnumerable testCases, IMessageSink messageSink, ITestFrameworkExecutionOptions executionOptions) { await remoteExecutor.RunTestCasesAsync(testCases.Cast(), CreateOptimizedRemoteMessageSink(messageSink), executionOptions); } } internal class NullSourceInformationProvider : ISourceInformationProvider { public static readonly NullSourceInformationProvider Instance = new NullSourceInformationProvider(); public ISourceInformation GetSourceInformation(ITestCase testCase) { return new Xunit.SourceInformation(); } public void Dispose() { } } internal class YieldingXunitTestFrameworkExecutor : XunitTestFrameworkExecutor { public YieldingXunitTestFrameworkExecutor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, IMessageSink diagnosticMessageSink) : base(assemblyName, sourceInformationProvider, diagnosticMessageSink) { } public async Task RunTestCasesAsync(IEnumerable testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions) { using (var assemblyRunner = new YieldingXunitTestAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions)) await assemblyRunner.RunAsync(); } protected override async void RunTestCases(IEnumerable testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions) { await RunTestCasesAsync(testCases, executionMessageSink, executionOptions); } } internal class YieldingXunitTestAssemblyRunner : XunitTestAssemblyRunner { public YieldingXunitTestAssemblyRunner(ITestAssembly testAssembly, IEnumerable testCases, IMessageSink diagnosticMessageSink, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions) : base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions) { } /// protected override async Task RunTestCollectionAsync(IMessageBus messageBus, ITestCollection testCollection, IEnumerable testCases, CancellationTokenSource cancellationTokenSource) { await Task.Yield(); return await new YieldingXunitTestCollectionRunner(testCollection, testCases, DiagnosticMessageSink, messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync(); } protected override async Task RunTestCollectionsAsync(IMessageBus messageBus, CancellationTokenSource cancellationTokenSource) { await Task.Yield(); return await base.RunTestCollectionsAsync(messageBus, cancellationTokenSource); } } internal class YieldingXunitTestCollectionRunner : XunitTestCollectionRunner { public YieldingXunitTestCollectionRunner(ITestCollection testCollection, IEnumerable testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(testCollection, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource) { } protected override async Task RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable testCases) { await Task.Yield(); var x = new YieldingXunitTestClassRunner(testClass, @class, testCases, DiagnosticMessageSink, MessageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), CancellationTokenSource, CollectionFixtureMappings); return await x.RunAsync(); } } internal class YieldingXunitTestClassRunner : XunitTestClassRunner { public YieldingXunitTestClassRunner(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource, IDictionary collectionFixtureMappings) : base(testClass, @class, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource, collectionFixtureMappings) { } protected override async Task RunTestMethodAsync(ITestMethod testMethod, IReflectionMethodInfo method, IEnumerable testCases, object[] constructorArguments) { await Task.Yield(); return await base.RunTestMethodAsync(testMethod, method, testCases, constructorArguments); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.TestRunners.Xunit/iOSApplicationEntryPoint.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; using Microsoft.DotNet.XHarness.TestRunners.Common; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Xunit; public abstract class iOSApplicationEntryPoint : iOSApplicationEntryPointBase { protected override TestRunner GetTestRunner(LogWriter logWriter) { if (!RuntimeFeature.IsDynamicCodeSupported) { var reflectionRunner = new ReflectionBasedXunitTestRunner(logWriter) { MaxParallelThreads = MaxParallelThreads }; ConfigureRunnerFilters(reflectionRunner, ApplicationOptions.Current); return reflectionRunner; } var runner = new XUnitTestRunner(logWriter) { MaxParallelThreads = MaxParallelThreads }; ConfigureRunnerFilters(runner, ApplicationOptions.Current); return runner; } protected override bool IsXunit => true; } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/AppBundleInformation.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared; public class AppBundleInformation { public string AppName { get; } public string BundleIdentifier { get; } public string AppPath { get; } public string? Variation { get; set; } public string LaunchAppPath { get; } public bool Supports32Bit { get; } public Extension? Extension { get; } public string? BundleExecutable { get; } public AppBundleInformation( string appName, string bundleIdentifier, string appPath, string launchAppPath, bool supports32b, Extension? extension = null, string? bundleExecutable = null) { AppName = appName; BundleIdentifier = bundleIdentifier; AppPath = appPath; LaunchAppPath = launchAppPath; Supports32Bit = supports32b; Extension = extension; BundleExecutable = bundleExecutable; } /// /// In case we don't have the app bundle (Info.plist) but only the bundle ID, we can create an approximate version of the info object. /// This is not ideal but "good enough" in most cases. /// /// Bundle information /// Approximation of bundle information public static AppBundleInformation FromBundleId(string bundleIdentifier) => new(bundleIdentifier, bundleIdentifier, string.Empty, string.Empty, false, bundleExecutable: bundleIdentifier); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/AppBundleInformationParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Xml; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared; public interface IAppBundleInformationParser { Task ParseFromProject(string projectFilePath, TestTarget target, string buildConfiguration); Task ParseFromAppBundle(string appPackagePath, TestTarget target, ILog log, CancellationToken cancellationToken = default); } public interface IAppBundleLocator { Task LocateAppBundle(XmlDocument projectFile, string projectFilePath, TestTarget target, string buildConfiguration); } public class AppBundleInformationParser : IAppBundleInformationParser { private const string PlistBuddyPath = "/usr/libexec/PlistBuddy"; private const string Armv7 = "armv7"; private readonly IProcessManager _processManager; private readonly IAppBundleLocator? _appBundleLocator; public AppBundleInformationParser(IProcessManager processManager, IAppBundleLocator? appBundleLocator = null) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _appBundleLocator = appBundleLocator; } public async Task ParseFromProject(string projectFilePath, TestTarget target, string buildConfiguration) { var csproj = new XmlDocument(); csproj.LoadWithoutNetworkAccess(projectFilePath); string projectDirectory = Path.GetDirectoryName(projectFilePath) ?? throw new DirectoryNotFoundException($"Cannot find directory of project '{projectFilePath}'"); string appName = csproj.GetAssemblyName(); string infoPlistPath = csproj.GetInfoPListInclude() ?? throw new InvalidOperationException("Couldn't locate PList include tag"); var infoPlist = new XmlDocument(); string plistPath = Path.Combine(projectDirectory, infoPlistPath.Replace('\\', Path.DirectorySeparatorChar)); infoPlist.LoadWithoutNetworkAccess(plistPath); string bundleIdentifier = infoPlist.GetCFBundleIdentifier(); string bundleExecutable = infoPlist.GetCFBundleExecutable(); Extension? extension = null; string extensionPointIdentifier = infoPlist.GetNSExtensionPointIdentifier(); if (!string.IsNullOrEmpty(extensionPointIdentifier)) { extension = extensionPointIdentifier.ParseFromNSExtensionPointIdentifier(); } var platform = target.IsSimulator() ? "iPhoneSimulator" : "iPhone"; string? appPath = null; if (_appBundleLocator != null) { appPath = await _appBundleLocator.LocateAppBundle(csproj, projectFilePath, target, buildConfiguration); } appPath ??= csproj.GetOutputPath(platform, buildConfiguration)?.Replace('\\', Path.DirectorySeparatorChar); appPath = Path.Combine( projectDirectory, appPath ?? string.Empty, appName + (extension != null ? ".appex" : ".app")); string? arch = csproj.GetMtouchArch(platform, buildConfiguration); bool supports32 = arch != null && (Contains(arch, "ARMv7") || Contains(arch, "i386")); if (!Directory.Exists(appPath)) { throw new DirectoryNotFoundException($"The app bundle directory `{appPath}` does not exist"); } string launchAppPath = target.ToRunMode() == RunMode.WatchOS ? Directory.GetDirectories(Path.Combine(appPath, "Watch"), "*.app")[0] : appPath; return new AppBundleInformation( appName, bundleIdentifier, appPath, launchAppPath, supports32, extension, bundleExecutable); } public async Task ParseFromAppBundle(string appPackagePath, TestTarget target, ILog log, CancellationToken cancellationToken = default) { string plistPath; if (target == TestTarget.MacCatalyst) { plistPath = Path.Combine(appPackagePath, "Contents", "Info.plist"); } else { plistPath = Path.Combine(appPackagePath, "Info.plist"); } if (!File.Exists(plistPath)) { throw new Exception($"Failed to find Info.plist inside the app bundle at: '{plistPath}'"); } var appName = await GetPlistProperty(plistPath, PListExtensions.BundleNamePropertyName, log, cancellationToken); var bundleIdentifier = await GetPlistProperty(plistPath, PListExtensions.BundleIdentifierPropertyName, log, cancellationToken); string supports32 = string.Empty; try { supports32 = await GetPlistProperty(plistPath, PListExtensions.RequiredDeviceCapabilities, log, cancellationToken); } catch (Exception e) { // The property might not be present log.WriteLine("Property UIRequiredDeviceCapabilities not present in Info.plist, assuming 32-bit is not supported. Error: " + e.Message); } string? bundleExecutable = null; try { bundleExecutable = await GetPlistProperty(plistPath, PListExtensions.BundleExecutablePropertyName, log, cancellationToken); } catch (Exception e) { log.WriteLine("Failed to locate the bundle executable property in Info.plist: " + e.Message); } string launchAppPath = target.ToRunMode() == RunMode.WatchOS ? Directory.GetDirectories(Path.Combine(appPackagePath, "Watch"), "*.app")[0] : appPackagePath; return new AppBundleInformation( appName: appName, bundleIdentifier: bundleIdentifier, appPath: appPackagePath, launchAppPath: launchAppPath, supports32b: Contains(supports32, Armv7), extension: null, bundleExecutable: bundleExecutable); } private async Task GetPlistProperty( string plistPath, string propertyName, ILog log, CancellationToken cancellationToken = default, int attempt = 1, int maxAttempts = 3) { var args = new[] { "-c", $"Print {propertyName}", plistPath, }; var commandOutput = new MemoryLog { Timestamp = false }; var processLog = new MemoryLog { Timestamp = false }; var result = await _processManager.ExecuteCommandAsync( PlistBuddyPath, args, processLog, commandOutput, commandOutput, TimeSpan.FromSeconds(10), cancellationToken: cancellationToken); if (!result.Succeeded) { if (result.TimedOut && attempt < maxAttempts) { log.WriteLine($"Attempt to get {propertyName} from {plistPath} timed out, retrying {attempt + 1} out of {maxAttempts}..."); return await GetPlistProperty(plistPath, propertyName, log, cancellationToken, attempt + 1, maxAttempts); } throw new Exception($"Failed to get bundle information: {commandOutput}"); } return commandOutput.ToString().Trim(); } // This method was added because .NET Standard 2.0 doesn't have case ignorant Contains() for String. private static bool Contains(string haystack, string needle) { return haystack.IndexOf(needle, StringComparison.InvariantCultureIgnoreCase) > -1; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Collections/BlockingEnumerableCollection.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Collections; // This is a collection whose enumerator will wait enumerating until // the collection has been marked as completed (but the enumerator can still // be created; this allows the creation of linq queries whose execution is // delayed until later). public class BlockingEnumerableCollection : IEnumerable where T : class { private readonly List _list = new(); private TaskCompletionSource _completed = new(); public int Count { get { WaitForCompletion(); return _list.Count; } } public void Add(T device) { _list.Add(device); } public void SetCompleted() => _completed.TrySetResult(true); private void WaitForCompletion() => _completed.Task.Wait(); public void Reset() { _completed = new TaskCompletionSource(); _list.Clear(); } public IEnumerator GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private class Enumerator : IEnumerator { private readonly BlockingEnumerableCollection _collection; private IEnumerator? _enumerator; public Enumerator(BlockingEnumerableCollection collection) { _collection = collection; } public T Current => _enumerator?.Current ?? throw new InvalidOperationException("Please call MoveNext() first!"); object IEnumerator.Current => _enumerator?.Current ?? throw new InvalidOperationException("Please call MoveNext() first!"); public void Dispose() => _enumerator?.Dispose(); public bool MoveNext() { _collection.WaitForCompletion(); if (_enumerator == null) { _enumerator = _collection._list.GetEnumerator(); } return _enumerator.MoveNext(); } public void Reset() => _enumerator?.Reset(); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Collections/IAsyncEnumerable.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Threading.Tasks; namespace Microsoft.DotNet.XHarness.iOS.Shared.Collections; public interface IAsyncEnumerable { Task ReadyTask { get; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/CrashSnapshotReporter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; namespace Microsoft.DotNet.XHarness.iOS.Shared; public interface ICrashSnapshotReporter { Task EndCaptureAsync(TimeSpan timeout); Task StartCaptureAsync(); } public class CrashSnapshotReporter : ICrashSnapshotReporter { private readonly IMlaunchProcessManager _processManager; private readonly ILog _log; private readonly ILogs _logs; private readonly bool _isDevice; private readonly string _deviceName; private readonly Func _tempFileProvider; private readonly string _symbolicateCrashPath; private HashSet _initialCrashes; public CrashSnapshotReporter(IMlaunchProcessManager processManager, ILog log, ILogs logs, bool isDevice, string deviceName, Func tempFileProvider = null) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _log = log ?? throw new ArgumentNullException(nameof(log)); _logs = logs ?? throw new ArgumentNullException(nameof(logs)); _isDevice = isDevice; _deviceName = deviceName; _tempFileProvider = tempFileProvider ?? Path.GetTempFileName; _symbolicateCrashPath = Path.Combine(processManager.XcodeRoot, "Contents", "SharedFrameworks", "DTDeviceKitBase.framework", "Versions", "A", "Resources", "symbolicatecrash"); if (!File.Exists(_symbolicateCrashPath)) { _symbolicateCrashPath = Path.Combine(processManager.XcodeRoot, "Contents", "SharedFrameworks", "DVTFoundation.framework", "Versions", "A", "Resources", "symbolicatecrash"); } if (!File.Exists(_symbolicateCrashPath)) { _symbolicateCrashPath = null; } } public async Task StartCaptureAsync() => _initialCrashes = await CreateCrashReportsSnapshotAsync(); public async Task EndCaptureAsync(TimeSpan timeout) { if (_initialCrashes == null) { throw new InvalidOperationException("CrashSnapshotReport capturing was ended without being started first!"); } _log.WriteLine($"No crash reports, waiting {(int)timeout.TotalSeconds} seconds for the crash report service..."); // Check for crash reports var stopwatch = Stopwatch.StartNew(); do { var newCrashFiles = await CreateCrashReportsSnapshotAsync(); newCrashFiles.ExceptWith(_initialCrashes); if (newCrashFiles.Count == 0) { if (stopwatch.Elapsed.TotalSeconds > timeout.TotalSeconds) { break; } else { await Task.Delay(TimeSpan.FromSeconds(1)); } continue; } _log.WriteLine("Found {0} new crash report(s)", newCrashFiles.Count); IEnumerable crashReports; if (!_isDevice) { crashReports = new List(newCrashFiles.Count); foreach (var path in newCrashFiles) { // It can happen that the crash log is still being written to so we have to retry int retry = 1; while (true) { try { var fileName = Path.GetFileName(path); _log.WriteLine($" - Adding {path}"); _logs.AddFile(path, $"Crash report: {fileName}"); _log.WriteLine($" Successfully copied {fileName}"); break; } catch (Exception e) { _log.WriteLine($" Attempt {retry} to copy a crash report failed: {e.Message}"); } if (retry == 3) { _log.WriteLine($" Failed to copy a crash report after {retry} retries"); break; } ++retry; await Task.Delay(TimeSpan.FromSeconds(2 * retry)); } } } else { // Download crash reports from the device. We put them in the project directory so that they're automatically deleted on wrench // (if we put them in /tmp, they'd never be deleted). crashReports = newCrashFiles .Select(async crash => await ProcessCrash(crash)) .Select(t => t.Result) .Where(c => c != null); } foreach (var cp in crashReports) { WrenchLog.WriteLine("AddFile: {0}", cp.FullPath); _log.WriteLine(" {0}", cp.FullPath); } break; } while (true); } private async Task ProcessCrash(string crashFile) { var name = Path.GetFileName(crashFile); var crashReportFile = _logs.Create(name, $"Crash report: {name}", timestamp: false); var args = new MlaunchArguments( new DownloadCrashReportArgument(crashFile), new DownloadCrashReportToArgument(crashReportFile.FullPath)); if (!string.IsNullOrEmpty(_deviceName)) { args.Add(new DeviceNameArgument(_deviceName)); } var result = await _processManager.ExecuteCommandAsync(args, _log, TimeSpan.FromMinutes(1)); if (result.Succeeded) { _log.WriteLine("Downloaded crash report {0} to {1}", crashFile, crashReportFile.FullPath); return await GetSymbolicateCrashReportAsync(crashReportFile); } else { _log.WriteLine("Could not download crash report {0}", crashFile); return null; } } private async Task GetSymbolicateCrashReportAsync(IFileBackedLog report) { if (_symbolicateCrashPath == null) { _log.WriteLine("Can't symbolicate {0} because the symbolicatecrash script {1} does not exist", report.FullPath, _symbolicateCrashPath); return report; } var name = Path.GetFileName(report.FullPath); var symbolicated = _logs.Create(Path.ChangeExtension(name, ".symbolicated.log"), $"Symbolicated crash report: {name}", timestamp: false); var environment = new Dictionary { { "DEVELOPER_DIR", Path.Combine(_processManager.XcodeRoot, "Contents", "Developer") } }; var result = await _processManager.ExecuteCommandAsync(_symbolicateCrashPath, new[] { report.FullPath }, symbolicated, TimeSpan.FromMinutes(1), environment); if (result.Succeeded) { _log.WriteLine("Symbolicated {0} successfully.", report.FullPath); return symbolicated; } else { _log.WriteLine("Failed to symbolicate {0}.", report.FullPath); return report; } } private async Task> CreateCrashReportsSnapshotAsync() { var crashes = new HashSet(); if (!_isDevice) { var dir = Path.Combine(Environment.GetEnvironmentVariable("HOME"), "Library", "Logs", "DiagnosticReports"); if (Directory.Exists(dir)) { crashes.UnionWith(Directory.EnumerateFiles(dir)); } } else { var tempFile = _tempFileProvider(); try { var args = new MlaunchArguments(new ListCrashReportsArgument(tempFile)); if (!string.IsNullOrEmpty(_deviceName)) { args.Add(new DeviceNameArgument(_deviceName)); } var result = await _processManager.ExecuteCommandAsync(args, _log, TimeSpan.FromMinutes(1)); if (result.Succeeded) { crashes.UnionWith(File.ReadAllLines(tempFile)); } } finally { File.Delete(tempFile); } } return crashes; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Execution/Arguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; namespace Microsoft.DotNet.XHarness.iOS.Shared.Execution; /// /// Specify the location of Apple SDKs, default to 'xcode-select' value. /// public sealed class SdkRootArgument : SingleValueArgument { public SdkRootArgument(string sdkPath) : base("sdkroot", sdkPath, false) { } } /// /// List the currently connected devices and their UDIDs. /// public sealed class ListDevicesArgument : SingleValueArgument { public ListDevicesArgument(string outputFile) : base("listdev", outputFile) { } } /// /// When listing devices, should mlaunch also scan for wireless devices. /// public sealed class ListWirelessDevicesArgument : SingleValueArgument { public ListWirelessDevicesArgument(bool wirelessEnabled) : base("list-wireless-devices", wirelessEnabled ? "true" : "false", true) { } } /// /// Write the syslog from the device to the console. /// public sealed class LogDevArgument : OptionArgument { public LogDevArgument() : base("logdev") { } } /// /// List the available simulators. The output is xml, and written to the specified file. /// public sealed class ListSimulatorsArgument : SingleValueArgument { public ListSimulatorsArgument(string outputFile) : base("listsim", outputFile) { } } /// /// Lists crash reports on the specified device /// public sealed class ListCrashReportsArgument : SingleValueArgument { public ListCrashReportsArgument(string outputFile) : base("list-crash-reports", outputFile) { } } /// /// Specifies the device type to launch the simulator as. /// public sealed class DeviceArgument : SingleValueArgument { public DeviceArgument(string deviceType) : base("device", deviceType) { } } /// /// Specify which device (when many are present) the [install|lauch|kill|log]dev command applies. /// public sealed class DeviceNameArgument : SingleValueArgument { private const string ArgName = "devname"; public DeviceNameArgument(string deviceName) : base(ArgName, deviceName, false) { } public DeviceNameArgument(IDevice device) : base(ArgName, device.UDID, false) { } } /// /// Install the specified iOS app bundle on the device. /// public sealed class InstallAppOnDeviceArgument : SingleValueArgument { public InstallAppOnDeviceArgument(string appPath) : base("installdev", appPath, false) { } } /// /// Install the specified iOS app bundle on the Simulator. /// public sealed class InstallAppOnSimulatorArgument : SingleValueArgument { public InstallAppOnSimulatorArgument(string appPath) : base("installsim", appPath, false) { } } /// /// Uninstall the specified bundle id from the device. /// public sealed class UninstallAppFromDeviceArgument : SingleValueArgument { public UninstallAppFromDeviceArgument(string appBundleId) : base("uninstalldevbundleid", appBundleId, false) { } } /// /// Specify the output format for some commands as Default. /// public sealed class DefaultOutputFormatArgument : SingleValueArgument { public DefaultOutputFormatArgument() : base("output-format", "Default") { } } /// /// Specify the output format for some commands as XML. /// public sealed class XmlOutputFormatArgument : SingleValueArgument { public XmlOutputFormatArgument() : base("output-format", "XML") { } } /// /// Download a crash report from the specified device. /// public sealed class DownloadCrashReportArgument : SingleValueArgument { public DownloadCrashReportArgument(string deviceName) : base("download-crash-report", deviceName) { } } /// /// Specifies the file to save the downloaded crash report. /// public sealed class DownloadCrashReportToArgument : SingleValueArgument { public DownloadCrashReportToArgument(string outputFile) : base("download-crash-report-to", outputFile) { } } /// /// Include additional data (which can take some time to fetch) when listing the connected devices. /// Only applicable when output format is xml. /// public sealed class ListExtraDataArgument : OptionArgument { public ListExtraDataArgument() : base("list-extra-data") { } } /// /// Attach native debugger. /// public sealed class AttachNativeDebuggerArgument : OptionArgument { public AttachNativeDebuggerArgument() : base("attach-native-debugger") { } } /// /// Attempt to disable memory limits for launched apps. /// This is just an attempt, some or all usual limits may still be enforced. /// public sealed class DisableMemoryLimitsArgument : OptionArgument { public DisableMemoryLimitsArgument() : base("disable-memory-limits") { } } public sealed class WaitForExitArgument : OptionArgument { public WaitForExitArgument() : base("wait-for-exit") { } } /// /// Launch the app with this command line argument. This must be specified multiple times for multiple arguments. /// public sealed class SetAppArgumentArgument : MlaunchArgument { private readonly string _value; public SetAppArgumentArgument(string value, bool isAppArg = false) { _value = value ?? throw new ArgumentNullException(nameof(value)); if (isAppArg) { _value = "-app-arg:" + _value; } } public override string AsCommandLineArgument() => "-argument=" + _value; } /// /// Set the environment variable in the application on startup. /// public sealed class SetEnvVariableArgument : MlaunchArgument { private readonly string _variableName; private readonly string _variableValue; public SetEnvVariableArgument(string variableName, object variableValue) { _variableName = variableName ?? throw new ArgumentNullException(nameof(variableName)); if (variableValue is bool b) { _variableValue = b.ToString().ToLowerInvariant(); } else { _variableValue = variableValue?.ToString() ?? throw new ArgumentNullException(nameof(variableValue)); } } public override string AsCommandLineArgument() => Escape($"-setenv={_variableName}={_variableValue}"); } /// /// Redirect the standard output for the simulated application to the specified file. /// public sealed class SetStdoutArgument : SingleValueArgument { public SetStdoutArgument(string targetFile) : base("stdout", targetFile) { } } /// /// Redirect the standard error for the simulated application to the specified file. /// public sealed class SetStderrArgument : SingleValueArgument { public SetStderrArgument(string targetFile) : base("stderr", targetFile) { } } /// /// Launch an app that is installed on device, specified by bundle identifier. /// public sealed class LaunchDeviceArgument : SingleValueArgument { private const string ArgName = "launchdev"; public LaunchDeviceArgument(string launchAppPath) : base(ArgName, launchAppPath, false) { } public LaunchDeviceArgument(AppBundleInformation appInfo) : base(ArgName, appInfo.AppPath, false) { } } /// /// Launch an app that is installed on device. /// public sealed class LaunchDeviceBundleIdArgument : SingleValueArgument { private const string ArgName = "launchdevbundleid"; public LaunchDeviceBundleIdArgument(string bundleId) : base(ArgName, bundleId, false) { } public LaunchDeviceBundleIdArgument(AppBundleInformation appInfo) : base(ArgName, appInfo.BundleIdentifier, false) { } } /// /// Launch the specified app in the simulator. /// public sealed class LaunchSimulatorAppArgument : SingleValueArgument { private const string ArgName = "launchsim"; public LaunchSimulatorAppArgument(string launchAppPath) : base(ArgName, launchAppPath, false) { } public LaunchSimulatorAppArgument(AppBundleInformation appInfo) : base(ArgName, appInfo.AppPath, false) { } } /// /// Launch the simulator only. /// public sealed class LaunchSimulatorArgument : OptionArgument { private const string ArgName = "launchsimulator"; public LaunchSimulatorArgument() : base(ArgName) { } } /// /// Launch the specified already installed app in the simulator. /// public sealed class LaunchSimulatorBundleArgument : SingleValueArgument { public LaunchSimulatorBundleArgument(AppBundleInformation appInfo) : base("launchsimbundleid", appInfo.BundleIdentifier, true) { } } /// /// Specify which simulator to launch. /// public sealed class SimulatorUDIDArgument : MlaunchArgument { private readonly string _udid; public SimulatorUDIDArgument(string udid) { _udid = udid ?? throw new ArgumentNullException(nameof(udid)); } public SimulatorUDIDArgument(IDevice device) { _udid = device?.UDID ?? throw new ArgumentNullException(nameof(device)); } public override string AsCommandLineArgument() => $"--device=:v2:udid={_udid}"; } /// /// Launch an app that is installed on device, specified by bundle identifier. /// public sealed class LaunchSimulatorExtensionArgument : MlaunchArgument { private readonly string _launchAppPath; private readonly string _bundleId; public LaunchSimulatorExtensionArgument(string launchAppPath, string bundleId) { _launchAppPath = launchAppPath ?? throw new ArgumentNullException(nameof(launchAppPath)); _bundleId = bundleId ?? throw new ArgumentNullException(nameof(bundleId)); } public override string AsCommandLineArgument() => "--launchsimbundleid " + "todayviewforextensions:" + Escape(_bundleId) + " " + "--observe-extension " + Escape(_launchAppPath); } /// /// Launch the specified bundle id in the simulator (which must already be installed). /// public sealed class LaunchDeviceExtensionArgument : MlaunchArgument { private readonly string _launchAppPath; private readonly string _bundleId; public LaunchDeviceExtensionArgument(string launchAppPath, string bundleId) { _launchAppPath = launchAppPath ?? throw new ArgumentNullException(nameof(launchAppPath)); _bundleId = bundleId ?? throw new ArgumentNullException(nameof(bundleId)); } public override string AsCommandLineArgument() => "--launchdevbundleid " + "todayviewforextensions:" + Escape(_bundleId) + " " + "--observe-extension " + Escape(_launchAppPath); } /// /// Set the verbosity level. Can be used repeatedly to lower the level. /// public sealed class VerbosityArgument : MlaunchArgument { public VerbosityArgument() { } public override string AsCommandLineArgument() => "-v"; } /// /// Create a tcp tunnel with the iOS device from the host. /// public sealed class TcpTunnelArgument : MlaunchArgument { private readonly int _port; public TcpTunnelArgument(int port) { if (port <= 0) { throw new ArgumentOutOfRangeException(nameof(port)); } _port = port; } public override string AsCommandLineArgument() => $"--tcp-tunnel={_port}:{_port}"; } /// /// Specify a timeout (in seconds) for the commands that doesn't have fixed duration. /// public sealed class TimeoutArgument : SingleValueArgument { public TimeoutArgument(double timeoutInSeconds) : base("timeout", timeoutInSeconds.ToString()) { } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Execution/EnviromentVariables.cs ================================================ namespace Microsoft.DotNet.XHarness.iOS.Shared.Execution; public static class EnviromentVariables { /// /// Env var that will tell the runner to start the execution of the tests automatically. /// public const string AutoStart = "NUNIT_AUTOSTART"; /// /// Env var that will tell the test application to exit once all the test have been ran. /// public const string AutoExit = "NUNIT_AUTOEXIT"; /// /// Env var that will tell the test application to enable network on the device (iOS). /// public const string EnableNetwork = "NUNIT_ENABLE_NETWORK"; /// /// Env var that will tell the test application to ignore those tests that required a permission to /// execute on the device. /// public const string DisableSystemPermissionTests = "DISABLE_SYSTEM_PERMISSION_TESTS"; /// /// Env var that provide the test application the name of the host. /// public const string HostName = "NUNIT_HOSTNAME"; /// /// Env var that provides the test application with the transport to use to communicate with the host. /// public const string Transport = "NUNIT_TRANSPORT"; /// /// Env var that provides the test application with the path to be used to store the execution logs. /// public const string LogFilePath = "NUNIT_LOG_FILE"; /// /// Env var that provide the test application the port to be used to connect with the host. /// public const string HostPort = "NUNIT_HOSTPORT"; /// /// Env var used to notify the test application that the communication will be done using a tcp tunnel /// over the usb cable. /// public const string UseTcpTunnel = "USE_TCP_TUNNEL"; /// /// Env var containing a tag that the test application will output once tests are finished to signalize it. /// public const string AppEndTag = "RUN_END_TAG"; /// /// Env var used to notify the test application that the output is expected to be xml. /// public const string EnableXmlOutput = "NUNIT_ENABLE_XML_OUTPUT"; /// /// Env var used to notify the test application the xml mode to be used. /// public const string XmlMode = "NUNIT_ENABLE_XML_MODE"; /// /// Env var used to pass the format of the xml used for results. /// public const string XmlVersion = "NUNIT_XML_VERSION"; /// /// Env var used to notify the test application that the test should be sorted by name. /// public const string SortByName = "NUNIT_SORTNAMES"; /// /// Env var used to notify the test application if all the tests should be ran by default. /// public const string RunAllTestsByDefault = "NUNIT_RUN_ALL"; /// /// Env var used to notify the test application which tests will be excluded. /// public const string SkippedMethods = "NUNIT_SKIPPED_METHODS"; /// /// Env var uses to notify the test application which test classes will be excluded. /// public const string SkippedClasses = "NUNIT_SKIPPED_CLASSES"; /// /// Env var that will tell the test application to enable code coverage collection. /// public const string EnableCoverage = "NUNIT_ENABLE_COVERAGE"; /// /// Env var that provides the test application with the output path for coverage results. /// public const string CoverageOutputPath = "NUNIT_COVERAGE_OUTPUT_PATH"; } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Execution/IMlaunchProcessManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Execution; public interface IMlaunchProcessManager : IMacOSProcessManager, IProcessManager { string MlaunchPath { get; } Task ExecuteCommandAsync( MlaunchArguments args, ILog log, TimeSpan timeout, Dictionary? environmentVariables = null, int verbosity = 0, CancellationToken? cancellationToken = null); Task ExecuteCommandAsync( MlaunchArguments args, ILog log, ILog stdoutLog, ILog stderrLog, TimeSpan timeout, Dictionary? environmentVariables = null, int verbosity = 0, CancellationToken? cancellationToken = null); Task RunAsync( Process process, MlaunchArguments args, ILog log, TimeSpan? timeout = null, Dictionary? environmentVariables = null, int verbosity = 0, CancellationToken? cancellationToken = null, bool? diagnostics = null); Task RunAsync( Process process, MlaunchArguments args, ILog log, ILog stdoutLog, ILog stderrLog, TimeSpan? timeout = null, Dictionary? environmentVariables = null, int verbosity = 0, CancellationToken? cancellationToken = null, bool? diagnostics = null); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Execution/MLaunchArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Microsoft.DotNet.XHarness.Common.Utilities; namespace Microsoft.DotNet.XHarness.iOS.Shared.Execution; // mlaunch is really important and has a lot of arguments that are known but // used to be passed as strings. This class allows to add arguments without // knowing the exact string and will also validate that an argument that // needs a value does contain the value public abstract class MlaunchArgument { public abstract string AsCommandLineArgument(); protected static string Escape(string value) => StringUtils.FormatArguments(value); public override bool Equals(object obj) => obj is MlaunchArgument arg && arg.AsCommandLineArgument() == AsCommandLineArgument(); public override int GetHashCode() => AsCommandLineArgument().GetHashCode(); } public abstract class SingleValueArgument : MlaunchArgument { private readonly string _argumentName; private readonly string _argumentValue; private readonly bool _useEqualSign; protected SingleValueArgument(string argumentName, string argumentValue, bool useEqualSign = true) { _argumentName = argumentName ?? throw new ArgumentNullException(nameof(argumentName)); _argumentValue = argumentValue ?? throw new ArgumentNullException(nameof(argumentValue)); _useEqualSign = useEqualSign; } public override string AsCommandLineArgument() { if (_useEqualSign) { return Escape($"--{_argumentName}={_argumentValue}"); } else { return $"--{_argumentName} {Escape(_argumentValue)}"; } } } public abstract class OptionArgument : MlaunchArgument { private readonly string _argumentName; protected OptionArgument(string argumentName) { _argumentName = argumentName ?? throw new ArgumentNullException(nameof(argumentName)); } public override string AsCommandLineArgument() => $"--{_argumentName}"; } public class MlaunchArguments : IEnumerable { private readonly List _arguments = new(); public MlaunchArguments(params MlaunchArgument[] args) { _arguments.AddRange(args); } public void Add(MlaunchArgument arg) => _arguments.Add(arg); public void AddRange(IEnumerable args) => _arguments.AddRange(args); public string AsCommandLine() => string.Join(" ", _arguments.Select(a => a.AsCommandLineArgument())); public IEnumerator GetEnumerator() => _arguments.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _arguments.GetEnumerator(); public override string ToString() => AsCommandLine(); public override bool Equals(object obj) => obj is MlaunchArguments arg && arg.AsCommandLine() == AsCommandLine(); public override int GetHashCode() => AsCommandLine().GetHashCode(); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Execution/MlaunchProcessManager.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Execution; public class MlaunchProcessManager : MacOSProcessManager, IMlaunchProcessManager { #region IMlaunchProcessManager implementation public string MlaunchPath { get; } public MlaunchProcessManager( string? xcodeRoot = null, string mlaunchPath = "/Library/Frameworks/Xamarin.iOS.framework/Versions/Current/bin/mlaunch") : base(xcodeRoot) { MlaunchPath = mlaunchPath; } public async Task ExecuteCommandAsync( MlaunchArguments args, ILog log, TimeSpan timeout, Dictionary? environmentVariables = null, int verbosity = 0, CancellationToken? cancellationToken = null) { using var p = new Process(); return await RunAsync(p, args, log, log, log, timeout, environmentVariables, verbosity, cancellationToken); } public async Task ExecuteCommandAsync( MlaunchArguments args, ILog log, ILog stdout, ILog stderr, TimeSpan timeout, Dictionary? environmentVariables = null, int verbosity = 0, CancellationToken? cancellationToken = null) { using var p = new Process(); return await RunAsync(p, args, log, stdout, stderr, timeout, environmentVariables, verbosity, cancellationToken); } public Task RunAsync( Process process, MlaunchArguments args, ILog log, TimeSpan? timeout = null, Dictionary? environmentVariables = null, int verbosity = 0, CancellationToken? cancellationToken = null, bool? diagnostics = null) => RunAsync(process, args, log, log, log, timeout, environmentVariables, verbosity, cancellationToken, diagnostics); public Task RunAsync( Process process, MlaunchArguments args, ILog log, ILog stdout, ILog stderr, TimeSpan? timeout = null, Dictionary? environmentVariables = null, int verbosity = 0, CancellationToken? cancellationToken = null, bool? diagnostics = null) { if (!args.Any(a => a is SdkRootArgument)) { args = new MlaunchArguments(args.Prepend(new SdkRootArgument(XcodeRoot)).ToArray()); } // Set verbosity for mlaunch (adds several '-v' args, each increasing the verbosity) for (var i = 0; i < verbosity; i++) { args.Add(new VerbosityArgument()); } process.StartInfo.FileName = MlaunchPath; process.StartInfo.Arguments = args.AsCommandLine(); return RunAsync(process, log, stdout, stderr, timeout, environmentVariables, cancellationToken, diagnostics); } #endregion } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Extension.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.iOS.Shared; public enum Extension { WatchKit2, TodayExtension, } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/Device.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public enum DeviceClass { Unknown = 0, iPhone, iPad, iPod, Watch, AppleTV, xrOS, } public class Device : IHardwareDevice { public Device( string deviceIdentifier, DeviceClass deviceClass, string name, string? buildVersion, string? productVersion, string? productType, string interfaceType, string? companionIdentifier = null, bool? isUsableForDebugging = null, bool isLocked = false, bool isPaired = false) { DeviceIdentifier = deviceIdentifier; DeviceClass = deviceClass; CompanionIdentifier = companionIdentifier; Name = name; BuildVersion = buildVersion; ProductVersion = productVersion; ProductType = productType; InterfaceType = interfaceType; IsUsableForDebugging = isUsableForDebugging; IsLocked = isLocked; IsPaired = isPaired; } public string DeviceIdentifier { get; } public DeviceClass DeviceClass { get; } public string? CompanionIdentifier { get; } public string Name { get; } public string? BuildVersion { get; } public string? ProductVersion { get; } public string? ProductType { get; } public string InterfaceType { get; } public bool? IsUsableForDebugging { get; } public bool IsLocked { get; } public bool IsPaired { get; } public string UDID => DeviceIdentifier; public string? OSVersion => ProductVersion; // Add a speed property that can be used to sort a list of devices according to speed. public int DebugSpeed => InterfaceType?.ToLowerInvariant() switch { "usb" => 0, // fastest null => 1, // mlaunch doesn't know - not sure when this can happen, but wifi is quite slow, so maybe this faster "wifi" => 2, // wifi is quite slow _ => 3, // Anything else is probably slower than wifi (e.g. watch). }; public DevicePlatform DevicePlatform => DeviceClass switch { _ when DeviceClass == DeviceClass.iPhone || DeviceClass == DeviceClass.iPod || DeviceClass == DeviceClass.iPad => DevicePlatform.iOS, _ when DeviceClass == DeviceClass.AppleTV => DevicePlatform.tvOS, _ when DeviceClass == DeviceClass.Watch => DevicePlatform.watchOS, _ when DeviceClass == DeviceClass.xrOS => DevicePlatform.xrOS, _ => DevicePlatform.Unknown, }; public bool Supports64Bit => Architecture == Architecture.ARM64; public bool Supports32Bit => DevicePlatform switch { DevicePlatform.iOS => ProductVersion != null && Version.Parse(ProductVersion).Major < 11, DevicePlatform.tvOS => false, DevicePlatform.watchOS => true, DevicePlatform.xrOS => false, _ => throw new NotImplementedException() }; public Architecture Architecture { get { var model = ProductType; if (model == null) { return Architecture.Unknown; } // https://www.theiphonewiki.com/wiki/Models if (model.StartsWith("iPhone", StringComparison.Ordinal)) { var identifier = model.Substring("iPhone".Length); var values = identifier.Split(','); switch (values[0]) { case "1": // iPhone (1) and iPhone 3G (2) return Architecture.ARMv6; case "2": // iPhone 3GS (1) case "3": // iPhone 4 (1-3) case "4": // iPhone 4S (1) return Architecture.ARMv7; case "5": // iPhone 5 (1-2) and iPhone 5c (3-4) return Architecture.ARMv7s; case "6": // iPhone 5s (1-2) case "7": // iPhone 6+ (1) and iPhone 6 (2) case "8": // iPhone 6s (1), iPhone 6s+ (2), iPhoneSE (4) case "9": // iPhone 7 (1,3) and iPhone 7+ (2,4) default: return Architecture.ARM64; } } // https://www.theiphonewiki.com/wiki/List_of_iPads if (model.StartsWith("iPad", StringComparison.Ordinal)) { var identifier = model.Substring("iPad".Length); var values = identifier.Split(','); switch (values[0]) { case "1": // iPad (1) case "2": // iPad 2 (1-4) and iPad Mini (5-7) case "3": // iPad 3 (1-3) and iPad 4 (4-6) return Architecture.ARMv7; case "4": // iPad Air (1-3), iPad Mini 2 (4-6) and iPad Mini 3 (7-9) case "5": // iPad Air 2 (3-4) case "6": // iPad Pro 9.7-inch (3-4), iPad Pro 12.9-inch (7-8) default: return Architecture.ARM64; } } // https://www.theiphonewiki.com/wiki/List_of_iPod_touches if (model.StartsWith("iPod", StringComparison.Ordinal)) { var identifier = model.Substring("iPod".Length); var values = identifier.Split(','); switch (values[0]) { case "1": // iPod touch (1) case "2": // iPod touch 2G (1) return Architecture.ARMv6; case "3": // iPod touch 3G (1) case "4": // iPod touch 4G (1) case "5": // iPod touch 5G (1) return Architecture.ARMv7; case "7": // iPod touch 6G (1) default: return Architecture.ARM64; } } // https://www.theiphonewiki.com/wiki/List_of_Apple_Watches if (model.StartsWith("Watch", StringComparison.Ordinal)) { var identifier = model.Substring("Watch".Length); var values = identifier.Split(','); switch (values[0]) { case "1": // Apple Watch (1st gen) case "2": // Apple Watch Series 1 and Series 2 case "3": // Apple Watch Series 3 return Architecture.ARMv7k; case "4": // Apple Watch Series 4 default: return Architecture.ARM64_32; } } // https://www.theiphonewiki.com/wiki/List_of_Apple_TVs if (model.StartsWith("AppleTV", StringComparison.Ordinal)) { return Architecture.ARM64; } throw new NotImplementedException(); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/HardwareDeviceLoader.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Xml; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Collections; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public interface IHardwareDeviceLoader : IDeviceLoader { IEnumerable ConnectedDevices { get; } IEnumerable Connected64BitIOS { get; } IEnumerable Connected32BitIOS { get; } IEnumerable ConnectedTV { get; } IEnumerable ConnectedWatch { get; } IEnumerable ConnectedWatch32_64 { get; } Task FindCompanionDevice(ILog log, IHardwareDevice device, CancellationToken cancellationToken = default); Task FindDevice( RunMode runMode, ILog log, bool includeLocked, bool includeWirelessDevices = true, CancellationToken cancellationToken = default); } public class HardwareDeviceLoader : IHardwareDeviceLoader { private readonly IMlaunchProcessManager _processManager; private bool _loaded; private readonly BlockingEnumerableCollection _connectedDevices = new(); private readonly SemaphoreSlim _semaphore = new(1); public IEnumerable ConnectedDevices => _connectedDevices; public IEnumerable Connected64BitIOS => _connectedDevices.Where(x => x.DevicePlatform == DevicePlatform.iOS && x.Supports64Bit); public IEnumerable Connected32BitIOS => _connectedDevices.Where(x => x.DevicePlatform == DevicePlatform.iOS && x.Supports32Bit); public IEnumerable ConnectedTV => _connectedDevices.Where(x => x.DevicePlatform == DevicePlatform.tvOS); public IEnumerable ConnectedWatch => _connectedDevices.Where(x => x.DevicePlatform == DevicePlatform.watchOS && x.Architecture == Architecture.ARMv7k); public IEnumerable ConnectedWatch32_64 => _connectedDevices.Where(x => x.DevicePlatform == DevicePlatform.watchOS && x.Architecture == Architecture.ARM64_32); public IEnumerable ConnectedxrOS => _connectedDevices.Where(x => x.DevicePlatform == DevicePlatform.xrOS); public HardwareDeviceLoader(IMlaunchProcessManager processManager) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); } public async Task LoadDevices( ILog log, bool includeLocked = false, bool forceRefresh = false, bool listExtraData = false, bool includeWirelessDevices = true, CancellationToken cancellationToken = default) { await _semaphore.WaitAsync(cancellationToken); if (_loaded) { if (!forceRefresh) { _semaphore.Release(); return; } _connectedDevices.Reset(); } var tmpfile = Path.GetTempFileName(); try { using (var process = new Process()) { var arguments = new MlaunchArguments( new ListDevicesArgument(tmpfile), new ListWirelessDevicesArgument(includeWirelessDevices), new XmlOutputFormatArgument(), new TimeoutArgument(0.5)); if (listExtraData) { arguments.Add(new ListExtraDataArgument()); } var task = _processManager.RunAsync(process, arguments, log, timeout: TimeSpan.FromSeconds(120), cancellationToken: cancellationToken); log.WriteLine("Launching {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); var result = await task; if (!result.Succeeded) { throw new Exception("Failed to list devices."); } var doc = new XmlDocument(); doc.LoadWithoutNetworkAccess(tmpfile); var devices = doc.SelectNodes("/MTouch/Device"); log.WriteLine($"Found {devices.Count} devices"); log.Flush(); foreach (XmlNode dev in devices) { Device d = GetDevice(dev); if (d == null) { continue; } if (!includeLocked && d.IsLocked) { log.WriteLine($"Skipping device {d.Name} ({d.DeviceIdentifier}) because it's locked."); continue; } if (d.IsUsableForDebugging.HasValue && !d.IsUsableForDebugging.Value) { log.WriteLine($"Skipping device {d.Name} ({d.DeviceIdentifier}) because it's not usable for debugging."); continue; } _connectedDevices.Add(d); } } _loaded = true; } catch (Exception e) { log.WriteLine($"Failed to parse device list: {e}"); log.Flush(); throw; } finally { _connectedDevices.SetCompleted(); if (_connectedDevices.Any()) { log.WriteLine($"Found following devices: '{string.Join("', '", _connectedDevices.Select(d => d.Name))}'"); } log.Flush(); File.Delete(tmpfile); _semaphore.Release(); } } public async Task FindDevice( RunMode runMode, ILog log, bool includeLocked, bool includeWirelessDevices = true, CancellationToken cancellationToken = default) { DeviceClass[] deviceClasses = runMode switch { RunMode.iOS => new[] { DeviceClass.iPhone, DeviceClass.iPad, DeviceClass.iPod }, RunMode.WatchOS => new[] { DeviceClass.Watch }, RunMode.TvOS => new[] { DeviceClass.AppleTV }, RunMode.xrOS => new[] { DeviceClass.xrOS },// Untested _ => throw new ArgumentException(nameof(runMode)), }; await LoadDevices( log, includeLocked: false, forceRefresh: false, includeWirelessDevices: includeWirelessDevices, cancellationToken: cancellationToken); IEnumerable compatibleDevices = ConnectedDevices.Where(v => deviceClasses.Contains(v.DeviceClass) && v.IsUsableForDebugging != false); IHardwareDevice device; if (!compatibleDevices.Any()) { throw new NoDeviceFoundException($"Could not find any applicable devices with device class(es): {string.Join(", ", deviceClasses)}"); } else if (compatibleDevices.Count() > 1) { device = compatibleDevices .OrderBy(dev => Version.TryParse(dev.ProductVersion, out Version v) ? v : new Version()) .First(); log.WriteLine("Found {0} devices for device class(es) '{1}': '{2}'. Selected: '{3}' (because it has the lowest version).", compatibleDevices.Count(), string.Join("', '", deviceClasses), string.Join("', '", compatibleDevices.Select((v) => v.Name).ToArray()), device.Name); } else { device = compatibleDevices.First(); } return device; } public async Task FindCompanionDevice(ILog log, IHardwareDevice device, CancellationToken cancellationToken = default) { await LoadDevices(log, false, false, cancellationToken: cancellationToken); var companion = ConnectedDevices.Where((v) => v.DeviceIdentifier == device.CompanionIdentifier); var count = companion.Count(); if (count == 0) { throw new Exception($"Could not find the companion device for '{device.Name}'"); } if (count > 1) { log.WriteLine("Found {0} companion devices for {1}?!?", count, device.Name); } return companion.First(); } private Device GetDevice(XmlNode deviceNode) { // get data, if we are missing some of them, we will return null, happens sometimes that we // have some empty nodes. We could do this with try/catch, but we want to throw the min amount // of exceptions. We do know that we will have issues with the parsing of the DeviceClass, check // the value, and if is there, get the rest, else return null var usable = deviceNode.SelectSingleNode("IsUsableForDebugging")?.InnerText; if (!Enum.TryParse(deviceNode.SelectSingleNode("DeviceClass")?.InnerText, true, out var deviceClass)) { return null; } return new Device( deviceIdentifier: deviceNode.SelectSingleNode("DeviceIdentifier")?.InnerText, deviceClass: deviceClass, companionIdentifier: deviceNode.SelectSingleNode("CompanionIdentifier")?.InnerText, name: deviceNode.SelectSingleNode("Name")?.InnerText, buildVersion: deviceNode.SelectSingleNode("BuildVersion")?.InnerText, productVersion: deviceNode.SelectSingleNode("ProductVersion")?.InnerText, productType: deviceNode.SelectSingleNode("ProductType")?.InnerText, interfaceType: deviceNode.SelectSingleNode("InterfaceType")?.InnerText, isUsableForDebugging: usable == null ? (bool?)null : usable == "True", isLocked: bool.TryParse(deviceNode.SelectSingleNode("IsLocked")?.InnerText, out var locked) && locked, isPaired: bool.TryParse(deviceNode.SelectSingleNode("IsPaired")?.InnerText, out var isPaired) && isPaired); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/IDevice.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public enum Architecture { Unknown, ARMv6, ARMv7, ARMv7k, ARMv7s, ARM64, ARM64_32, i386, x86_64, } public enum DevicePlatform { Unknown, iOS, tvOS, watchOS, xrOS, macOS, } public interface IDevice { string Name { get; } string UDID { get; } string OSVersion { get; } } public static class DevicePlatformExtensions { public static string AsString(this DevicePlatform value) => value switch { DevicePlatform.iOS => "iOS", DevicePlatform.tvOS => "tvOS", DevicePlatform.watchOS => "watchOS", DevicePlatform.xrOS => "xrOS", DevicePlatform.macOS => "macOS", _ => throw new Exception($"Unknown platform: {value}"), }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/IDeviceLoader.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public interface IDeviceLoader { Task LoadDevices( ILog log, bool includeLocked = false, bool forceRefresh = false, bool listExtraData = false, bool includeWirelessDevices = true, CancellationToken cancellationToken = default); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/IHardwareDevice.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public interface IHardwareDevice : IDevice { string DeviceIdentifier { get; } DeviceClass DeviceClass { get; } string? CompanionIdentifier { get; } string? BuildVersion { get; } string? ProductVersion { get; } string? ProductType { get; } string InterfaceType { get; } bool? IsUsableForDebugging { get; } bool IsLocked { get; } bool IsPaired { get; } int DebugSpeed { get; } DevicePlatform DevicePlatform { get; } bool Supports64Bit { get; } bool Supports32Bit { get; } Architecture Architecture { get; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/ISimulatorDevice.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public interface ISimulatorDevice : IDevice { string SimRuntime { get; set; } string SimDeviceType { get; set; } public DeviceState State { get; } string DataPath { get; set; } string LogPath { get; set; } string SystemLog { get; } bool IsWatchSimulator { get; } Task Erase(ILog log); Task Shutdown(ILog log); Task PrepareSimulator(ILog log, params string[] bundleIdentifiers); Task KillEverything(ILog log); Task Boot(ILog log, CancellationToken cancellationToken); Task GetAppBundlePath(ILog log, string bundleIdentifier, CancellationToken cancellationToken); } public enum DeviceState { Unknown = 0, ShuttingDown = 2, Shutdown = 1, Booting = 3, Booted = 4, } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/ISimulatorLoader.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public interface ISimulatorLoader : IDeviceLoader { IEnumerable SupportedRuntimes { get; } IEnumerable SupportedDeviceTypes { get; } IEnumerable AvailableDevices { get; } IEnumerable AvailableDevicePairs { get; } Task FindCompanionDevice(ILog log, ISimulatorDevice device, CancellationToken cancellationToken = default); IEnumerable SelectDevices(TestTarget target, ILog log, bool minVersion, CancellationToken cancellationToken = default); IEnumerable SelectDevices(TestTargetOs target, ILog log, bool minVersion, CancellationToken cancellationToken = default); Task<(ISimulatorDevice Simulator, ISimulatorDevice? CompanionSimulator)> FindSimulators( TestTarget target, ILog log, bool createIfNeeded = true, bool minVersion = false, CancellationToken cancellationToken = default); Task<(ISimulatorDevice Simulator, ISimulatorDevice? CompanionSimulator)> FindSimulators( TestTargetOs target, ILog log, bool createIfNeeded = true, bool minVersion = false, CancellationToken cancellationToken = default); Task<(ISimulatorDevice Simulator, ISimulatorDevice? CompanionSimulator)> FindSimulators( TestTargetOs target, ILog log, int retryCount, bool createIfNeeded = true, bool minVersion = false, CancellationToken cancellationToken = default); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/SimDevicePair.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public class SimDevicePair { public string UDID { get; } public string Companion { get; } public string Gizmo { get; } public SimDevicePair(string UDID, string companion, string gizmo) { this.UDID = UDID; Companion = companion; Gizmo = gizmo; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/SimDeviceSpecification.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public class SimDeviceSpecification { public SimulatorDevice Main { get; } public SimulatorDevice Companion { get; } // the phone for watch devices public SimDeviceSpecification(SimulatorDevice main, SimulatorDevice companion) { Main = main; Companion = companion; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/SimDeviceType.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public class SimDeviceType { public string Name { get; } public string Identifier { get; } public string ProductFamilyId { get; } public long MinRuntimeVersion { get; } public long MaxRuntimeVersion { get; } public bool Supports64Bits { get; } public SimDeviceType(string name, string identifier, string productFamilyId, long minRuntimeVersion, long maxRuntimeVersion, bool supports64Bits) { Name = name; Identifier = identifier; ProductFamilyId = productFamilyId; MinRuntimeVersion = minRuntimeVersion; MaxRuntimeVersion = maxRuntimeVersion; Supports64Bits = supports64Bits; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/SimRuntime.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public class SimRuntime { public string Name { get; } public string Identifier { get; } public long Version { get; } public SimRuntime(string name, string identifier, long version) { Name = name; Identifier = identifier; Version = version; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/SimulatorDevice.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public class SimulatorDevice : ISimulatorDevice { private readonly IMlaunchProcessManager _processManager; private readonly ITCCDatabase _tCCDatabase; public string UDID { get; set; } public string Name { get; set; } public string SimRuntime { get; set; } public string SimDeviceType { get; set; } public DeviceState State { get; set; } = DeviceState.Unknown; public string DataPath { get; set; } public string LogPath { get; set; } public string SystemLog => Path.Combine(LogPath, "system.log"); public SimulatorDevice(IMlaunchProcessManager processManager, ITCCDatabase tccDatabase) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _tCCDatabase = tccDatabase ?? throw new ArgumentNullException(nameof(tccDatabase)); } public bool IsWatchSimulator => SimRuntime.StartsWith("com.apple.CoreSimulator.SimRuntime.watchOS", StringComparison.Ordinal); public string OSVersion { get { var v = SimRuntime.Substring("com.apple.CoreSimulator.SimRuntime.".Length); var dash = v.IndexOf('-'); return v.Substring(0, dash) + " " + v.Substring(dash + 1).Replace('-', '.'); } } public async Task Erase(ILog log) { // here we don't care if execution fails. // erase the simulator (make sure the device isn't running first) await _processManager.ExecuteXcodeCommandAsync("simctl", new[] { "shutdown", UDID }, log, TimeSpan.FromMinutes(1)); await _processManager.ExecuteXcodeCommandAsync("simctl", new[] { "erase", UDID }, log, TimeSpan.FromMinutes(1)); // boot & shutdown to make sure it actually works await _processManager.ExecuteXcodeCommandAsync("simctl", new[] { "boot", UDID }, log, TimeSpan.FromMinutes(1)); await _processManager.ExecuteXcodeCommandAsync("simctl", new[] { "shutdown", UDID }, log, TimeSpan.FromMinutes(1)); } public async Task Shutdown(ILog log) { await _processManager.ExecuteXcodeCommandAsync("simctl", new[] { "shutdown", UDID }, log, TimeSpan.FromMinutes(1)); State = DeviceState.Shutdown; } public async Task KillEverything(ILog log) { await _processManager.ExecuteCommandAsync("launchctl", new[] { "remove", "com.apple.CoreSimulator.CoreSimulatorService" }, log, TimeSpan.FromSeconds(10)); var toKill = new string[] { "iPhone Simulator", "iOS Simulator", "Simulator", "Simulator (Watch)", "com.apple.CoreSimulator.CoreSimulatorService", "ibtoold" }; var args = new List { "-9" }; args.AddRange(toKill); await _processManager.ExecuteCommandAsync("killall", args, log, TimeSpan.FromSeconds(10)); State = DeviceState.Shutdown; var dirsToBeDeleted = new[] { Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), "Library", "Saved Application State", "com.apple.watchsimulator.savedState"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), "Library", "Saved Application State", "com.apple.iphonesimulator.savedState"), }; foreach (var dir in dirsToBeDeleted) { try { if (Directory.Exists(dir)) { Directory.Delete(dir, true); } } catch (Exception e) { log.WriteLine("Could not delete the directory '{0}': {1}", dir, e.Message); } } } private async Task OpenSimulator(ILog log) { string simulator_app; if (IsWatchSimulator && _processManager.XcodeVersion.Major < 9) { simulator_app = Path.Combine(_processManager.XcodeRoot, "Contents", "Developer", "Applications", "Simulator (Watch).app"); } else { simulator_app = Path.Combine(_processManager.XcodeRoot, "Contents", "Developer", "Applications", "Simulator.app"); if (!Directory.Exists(simulator_app)) { simulator_app = Path.Combine(_processManager.XcodeRoot, "Contents", "Developer", "Applications", "iOS Simulator.app"); } } await _processManager.ExecuteCommandAsync("open", new[] { "-a", simulator_app, "--args", "-CurrentDeviceUDID", UDID }, log, TimeSpan.FromSeconds(15)); } public async Task PrepareSimulator(ILog log, params string[] bundleIdentifiers) { // Kill all existing processes await KillEverything(log); // We shutdown and erase all simulators. await Erase(log); var tccDBCandidates = new string[] { Path.Combine(DataPath, "data", "Library", "TCC", "TCC.db"), Path.Combine(DataPath, "Library", "TCC", "TCC.db"), }; var tccDB = ""; var checkForTccDB = new Func(() => { tccDB = tccDBCandidates.FirstOrDefault(candidate => File.Exists(candidate)) ?? ""; return !string.IsNullOrEmpty(tccDB); }); if (!checkForTccDB()) { log.WriteLine("Booting the simulator to create TCC.db"); await _processManager.ExecuteXcodeCommandAsync("simctl", new[] { "boot", UDID }, log, TimeSpan.FromMinutes(1)); var tccCreationTimeout = 60; var watch = new Stopwatch(); watch.Start(); while (!checkForTccDB() && watch.Elapsed.TotalSeconds < tccCreationTimeout) { log.WriteLine("Waiting for simulator to create TCC.db... {0}", (int)(tccCreationTimeout - watch.Elapsed.TotalSeconds)); await Task.Delay(TimeSpan.FromSeconds(0.250)); } } var result = true; if (File.Exists(tccDB)) { log.WriteLine("TCC.db found for the simulator {0} (SimRuntime={1} and SimDeviceType={2}): {3}", UDID, SimRuntime, SimDeviceType, tccDB); bundleIdentifiers = bundleIdentifiers.Where(id => !string.IsNullOrEmpty(id)).ToArray(); if (bundleIdentifiers.Any()) { log.WriteLine($"Storing adequate permissions in TCC.db to prevent dialog boxes in the test apps: {string.Join(", ", bundleIdentifiers)}", UDID); result &= await _tCCDatabase.AgreeToPromptsAsync(SimRuntime, tccDB, UDID, log, bundleIdentifiers); } } else { log.WriteLine("TCC.db not found for the simulator {0} (SimRuntime={1} and SimDeviceType={2}). Candidates:\n{3}", UDID, SimRuntime, SimDeviceType, string.Join ("\n\t", tccDBCandidates)); } // Make sure we're in a clean state await KillEverything(log); // Make 100% sure we're shutdown await Shutdown(log); return result; } public async Task Boot(ILog log, CancellationToken cancellationToken) { if (State == DeviceState.Booted) { log.WriteLine($"Simulator '{Name}' is already booted"); return true; } log.WriteLine($"Booting simulator '{Name}'"); var args = new MlaunchArguments { new SimulatorUDIDArgument(this), new LaunchSimulatorArgument(), }; var watch = Stopwatch.StartNew(); var result = await _processManager.ExecuteCommandAsync( args, log, TimeSpan.FromSeconds(30), verbosity: 2, cancellationToken: cancellationToken); if (!result.Succeeded) { log.WriteLine($"Failed to boot the simulator '{Name}'"); return false; } log.WriteLine($"Simulator '{Name}' booted in {(int)watch.Elapsed.TotalSeconds} seconds"); State = DeviceState.Booted; return true; } public async Task GetAppBundlePath(ILog log, string bundleIdentifier, CancellationToken cancellationToken) { log.WriteLine($"Querying '{Name}' for bundle path of '{bundleIdentifier}'.."); var output = new MemoryLog() { Timestamp = false }; var result = await _processManager.ExecuteXcodeCommandAsync( "simctl", new[] { "get_app_container", UDID, bundleIdentifier }, log, output, output, TimeSpan.FromSeconds(30)); if (!result.Succeeded) { throw new Exception($"Failed to get information for '{bundleIdentifier}'. Please check the app is installed"); } var bundlePath = output.ToString().Trim(); log.WriteLine($"Found installed app bundle at '{bundlePath}'"); return bundlePath; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/SimulatorLoader.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Collections; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public class SimulatorLoader : ISimulatorLoader { private readonly BlockingEnumerableCollection _supportedRuntimes = new(); private readonly BlockingEnumerableCollection _supportedDeviceTypes = new(); private readonly BlockingEnumerableCollection _availableDevices = new(); private readonly BlockingEnumerableCollection _availableDevicePairs = new(); private readonly SemaphoreSlim _semaphore = new(1); private readonly IMlaunchProcessManager _processManager; private readonly ISimulatorSelector _simulatorSelector; private bool _loaded; public IEnumerable SupportedRuntimes => _supportedRuntimes; public IEnumerable SupportedDeviceTypes => _supportedDeviceTypes; public IEnumerable AvailableDevices => _availableDevices; public IEnumerable AvailableDevicePairs => _availableDevicePairs; public SimulatorLoader(IMlaunchProcessManager processManager, ISimulatorSelector? simulatorSelector = null) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _simulatorSelector = simulatorSelector ?? new DefaultSimulatorSelector(); } public async Task LoadDevices( ILog log, bool includeLocked = false, bool forceRefresh = false, bool listExtraData = false, bool includeWirelessDevices = true, CancellationToken cancellationToken = default) { await _semaphore.WaitAsync(cancellationToken); cancellationToken.ThrowIfCancellationRequested(); if (_loaded) { if (!forceRefresh) { _semaphore.Release(); return; } _supportedRuntimes.Reset(); _supportedDeviceTypes.Reset(); _availableDevices.Reset(); _availableDevicePairs.Reset(); } var tmpfile = Path.GetTempFileName(); try { var arguments = new MlaunchArguments( new ListSimulatorsArgument(tmpfile), new XmlOutputFormatArgument()); var result = await _processManager.ExecuteCommandAsync(arguments, log, timeout: TimeSpan.FromMinutes(6), cancellationToken: cancellationToken); cancellationToken.ThrowIfCancellationRequested(); if (!result.Succeeded) { // mlaunch can sometimes return 0 but hang and timeout. It still outputs returns valid content to the tmp file log.WriteLine($"mlaunch failed when listing simulators but trying to parse the results anyway"); } var fileInfo = new FileInfo(tmpfile); if (!fileInfo.Exists || fileInfo.Length == 0) { throw new Exception($"Failed to list simulators - no XML with devices found. " + $"mlaunch {(result.TimedOut ? "timed out" : "exited")} with {result.ExitCode})"); } log.WriteLine($"Simulator listing finished ({Math.Ceiling(((double)fileInfo.Length) / 1024)} kB)"); var simulatorData = new XmlDocument(); simulatorData.LoadWithoutNetworkAccess(tmpfile); foreach (XmlNode? sim in simulatorData.SelectNodes("/MTouch/Simulator/SupportedRuntimes/SimRuntime")) { if (sim == null) { continue; } _supportedRuntimes.Add(new SimRuntime( name: sim.SelectSingleNode("Name").InnerText, identifier: sim.SelectSingleNode("Identifier").InnerText, version: long.Parse(sim.SelectSingleNode("Version").InnerText))); } foreach (XmlNode? sim in simulatorData.SelectNodes("/MTouch/Simulator/SupportedDeviceTypes/SimDeviceType")) { if (sim == null) { continue; } _supportedDeviceTypes.Add(new SimDeviceType( name: sim.SelectSingleNode("Name").InnerText, identifier: sim.SelectSingleNode("Identifier").InnerText, productFamilyId: sim.SelectSingleNode("ProductFamilyId").InnerText, minRuntimeVersion: long.Parse(sim.SelectSingleNode("MinRuntimeVersion").InnerText), maxRuntimeVersion: long.Parse(sim.SelectSingleNode("MaxRuntimeVersion").InnerText), supports64Bits: bool.Parse(sim.SelectSingleNode("Supports64Bits").InnerText))); } foreach (XmlNode? sim in simulatorData.SelectNodes("/MTouch/Simulator/AvailableDevices/SimDevice")) { if (sim == null) { continue; } _availableDevices.Add(new SimulatorDevice(_processManager, new TCCDatabase(_processManager)) { Name = sim.Attributes["Name"].Value, UDID = sim.Attributes["UDID"].Value, State = sim.Attributes["State"]?.Value switch { "Booting" => DeviceState.Booting, "Booted" => DeviceState.Booted, "ShuttingDown" => DeviceState.ShuttingDown, "Shutdown" => DeviceState.Shutdown, _ => DeviceState.Unknown, }, SimRuntime = sim.SelectSingleNode("SimRuntime").InnerText, SimDeviceType = sim.SelectSingleNode("SimDeviceType").InnerText, DataPath = sim.SelectSingleNode("DataPath").InnerText, LogPath = sim.SelectSingleNode("LogPath").InnerText, }); } var sim_device_pairs = simulatorData. SelectNodes("/MTouch/Simulator/AvailableDevicePairs/SimDevicePair"). Cast(). // There can be duplicates, so remove those. Distinct(new SimulatorXmlNodeComparer()); foreach (XmlNode sim in sim_device_pairs) { _availableDevicePairs.Add(new SimDevicePair( UDID: sim.Attributes["UDID"].Value, companion: sim.SelectSingleNode("Companion").InnerText, gizmo: sim.SelectSingleNode("Gizmo").InnerText)); } _loaded = true; } finally { _supportedRuntimes.SetCompleted(); _supportedDeviceTypes.SetCompleted(); _availableDevices.SetCompleted(); _availableDevicePairs.SetCompleted(); File.Delete(tmpfile); _semaphore.Release(); } } private string CreateName(string deviceType, string runtime) { var runtimeName = _supportedRuntimes?.Where(v => v.Identifier == runtime).FirstOrDefault()?.Name ?? Path.GetExtension(runtime).Substring(1); var deviceName = _supportedDeviceTypes?.Where(v => v.Identifier == deviceType).FirstOrDefault()?.Name ?? Path.GetExtension(deviceType).Substring(1); return $"{deviceName} ({runtimeName}) - created by XHarness"; } // Will return all devices that match the runtime + devicetype (even if a new device was created, any other devices will also be returned) private async Task> FindOrCreateDevicesAsync( ILog log, string runtime, string devicetype, bool force = false, CancellationToken cancellationToken = default) { if (runtime is null) { throw new ArgumentNullException(nameof(runtime)); } if (devicetype is null) { throw new ArgumentNullException(nameof(devicetype)); } IEnumerable devices; if (!force) { if (!_loaded) { await LoadDevices(log, cancellationToken: cancellationToken); } devices = AvailableDevices.Where(v => v.SimRuntime == runtime && v.SimDeviceType == devicetype); if (devices.Any()) { return devices; } } var args = new[] { "create", CreateName(devicetype, runtime), devicetype, runtime }; var rv = await _processManager.ExecuteXcodeCommandAsync("simctl", args, log, TimeSpan.FromMinutes(1), cancellationToken); if (!rv.Succeeded) { var message = $"Could not create device{Environment.NewLine}" + $"runtime: {runtime}{Environment.NewLine}" + $"device type: {devicetype}"; log.WriteLine(message); throw new NoDeviceFoundException(message); } await LoadDevices(log, forceRefresh: true, cancellationToken: cancellationToken); devices = AvailableDevices.Where((ISimulatorDevice v) => v.SimRuntime == runtime && v.SimDeviceType == devicetype); if (!devices.Any()) { var message = $"Simulator not found after creating it{Environment.NewLine}" + $"runtime: {runtime}{Environment.NewLine}" + $"device type: {devicetype}"; log.WriteLine(message); throw new NoDeviceFoundException(message); } return devices; } private async Task CreateDevicePair( ILog log, ISimulatorDevice device, ISimulatorDevice companion_device, string runtime, string devicetype, bool createDevice, CancellationToken cancellationToken = default) { if (createDevice) { // watch device is already paired to some other phone. Create a new watch device var matchingDevices = await FindOrCreateDevicesAsync(log, runtime, devicetype, force: true, cancellationToken); var unPairedDevices = matchingDevices.Where(v => !AvailableDevicePairs.Any(p => p.Gizmo == v.UDID)); if (device != null) { // If we're creating a new watch device, assume that the one we were given is not usable. unPairedDevices = unPairedDevices.Where(v => v.UDID != device.UDID); } if (unPairedDevices?.Any() != true) { return false; } device = unPairedDevices.First(); } log.WriteLine($"Creating device pair for '{device.Name}' and '{companion_device.Name}'"); var capturedLog = new StringBuilder(); var pairLog = new CallbackLog((value) => { log.Write(value); capturedLog.Append(value); }); var args = new[] { "pair", device.UDID, companion_device.UDID }; var rv = await _processManager.ExecuteXcodeCommandAsync("simctl", args, pairLog, TimeSpan.FromMinutes(1), cancellationToken); if (!rv.Succeeded) { if (!createDevice) { var try_creating_device = false; var captured_log = capturedLog.ToString(); try_creating_device |= captured_log.Contains("At least one of the requested devices is already paired with the maximum number of supported devices and cannot accept another pairing."); try_creating_device |= captured_log.Contains("The selected devices are already paired with each other."); if (try_creating_device) { log.WriteLine($"Could not create device pair for '{device.Name}' ({device.UDID}) and '{companion_device.Name}' ({companion_device.UDID}), but will create a new watch device and try again."); return await CreateDevicePair(log, device, companion_device, runtime, devicetype, true); } } log.WriteLine($"Could not create device pair for '{device.Name}' ({device.UDID}) and '{companion_device.Name}' ({companion_device.UDID})"); return false; } return true; } private async Task FindOrCreateDevicePairAsync( ILog log, IEnumerable devices, IEnumerable companionDevices, CancellationToken cancellationToken = default) { // Check if we already have a device pair with the specified devices var pairs = AvailableDevicePairs.Where(pair => { if (!devices.Any(v => v.UDID == pair.Gizmo)) { return false; } if (!companionDevices.Any(v => v.UDID == pair.Companion)) { return false; } return true; }); if (!pairs.Any()) { // No device pair. Create one. // First check if the watch is already paired var unPairedDevices = devices.Where(v => !AvailableDevicePairs.Any(p => p.Gizmo == v.UDID)); var unpairedDevice = unPairedDevices.FirstOrDefault(); var companion_device = companionDevices.First(); var device = devices.First(); if (!await CreateDevicePair( log, unpairedDevice, companion_device, device.SimRuntime, device.SimDeviceType, unpairedDevice == null, cancellationToken: cancellationToken)) { return null; } await LoadDevices(log, forceRefresh: true, cancellationToken: cancellationToken); pairs = AvailableDevicePairs.Where((pair) => { if (!devices.Any(v => v.UDID == pair.Gizmo)) { return false; } if (!companionDevices.Any(v => v.UDID == pair.Companion)) { return false; } return true; }); } return pairs.FirstOrDefault(); } /// /// This is a new implementation that respects also target OS version and if that one is specified, looks for that specific simulator. /// Old implementation of FindSimulators is kept intact because it is being used in Xamarin Mac/iOS. /// public async Task<(ISimulatorDevice Simulator, ISimulatorDevice? CompanionSimulator)> FindSimulators( TestTargetOs target, ILog log, bool createIfNeeded = true, bool minVersion = false, CancellationToken cancellationToken = default) { var runtimePrefix = _simulatorSelector.GetRuntimePrefix(target); var runtimeVersion = target.OSVersion; if (runtimeVersion == null) { if (!_loaded) { await LoadDevices(log, cancellationToken: cancellationToken); } string? firstOsVersion = _supportedRuntimes .Where(r => r.Identifier.StartsWith(runtimePrefix)) .OrderByDescending(r => r.Identifier) .FirstOrDefault()? .Identifier .Substring(runtimePrefix.Length); runtimeVersion = firstOsVersion ?? throw new NoDeviceFoundException($"Failed to find a suitable OS runtime version for {target.AsString()}"); } string simulatorRuntime = runtimePrefix + runtimeVersion.Replace('.', '-'); string simulatorDeviceType = _simulatorSelector.GetDeviceType(target, minVersion); // TODO: Allow to specify companion runtime _simulatorSelector.GetCompanionRuntimeAndDeviceType(target, minVersion, out var companionRuntime, out var companionDeviceType); var devices = await FindOrCreateDevicesAsync(log, simulatorRuntime, simulatorDeviceType, cancellationToken: cancellationToken); IEnumerable? companionDevices = null; if (companionRuntime != null && companionDeviceType != null) { companionDevices = await FindOrCreateDevicesAsync(log, companionRuntime, companionDeviceType, cancellationToken: cancellationToken); } if (devices?.Any() != true) { throw new Exception($"Could not find or create devices{Environment.NewLine}" + $"runtime: {simulatorRuntime}{Environment.NewLine}" + $"device type: {simulatorDeviceType}"); } ISimulatorDevice? simulator = null; ISimulatorDevice? companionSimulator = null; if (companionRuntime == null) { simulator = _simulatorSelector.SelectSimulator(devices); } else { if (companionDevices?.Any() != true) { throw new Exception($"Could not find or create companion devices{Environment.NewLine}" + $"runtime: {companionRuntime}{Environment.NewLine}" + $"device type: {companionDeviceType}"); } var pair = await FindOrCreateDevicePairAsync(log, devices, companionDevices); if (pair == null) { throw new Exception($"Could not find or create device pair{Environment.NewLine}" + $"runtime: {companionRuntime}{Environment.NewLine}" + $"device type: {companionDeviceType}"); } simulator = devices.First(v => v.UDID == pair.Gizmo); companionSimulator = companionDevices.First(v => v.UDID == pair.Companion); } if (simulator == null) { throw new Exception($"Could not find simulator{Environment.NewLine}" + $"runtime: {simulatorRuntime}{Environment.NewLine}" + $"device type: {simulatorDeviceType}"); } log.WriteLine("Found simulator: {0} {1}", simulator.Name, simulator.UDID); if (companionSimulator != null) { log.WriteLine("Found companion simulator: {0} {1}", companionSimulator.Name, companionSimulator.UDID); } return (simulator, companionSimulator); } public Task<(ISimulatorDevice Simulator, ISimulatorDevice? CompanionSimulator)> FindSimulators( TestTarget target, ILog log, bool createIfNeeded = true, bool minVersion = false, CancellationToken cancellationToken = default) { TestTargetOs testTarget = target switch { TestTarget.Simulator_iOS64 => new TestTargetOs(target, minVersion ? SdkVersions.MiniOSSimulator : SdkVersions.MaxiOSSimulator), TestTarget.Simulator_tvOS => new TestTargetOs(target, minVersion ? SdkVersions.MinTVOSSimulator : SdkVersions.MaxTVOSSimulator), TestTarget.Simulator_watchOS => new TestTargetOs(target, minVersion ? SdkVersions.MinWatchOSSimulator : SdkVersions.MaxWatchOSSimulator), TestTarget.Simulator_xrOS => new TestTargetOs(target, minVersion ? SdkVersions.MinxrOSSimulator : SdkVersions.MaxxrOSSimulator), _ => throw new Exception(string.Format("Invalid simulator target: {0}", target)) }; return FindSimulators(testTarget, log, createIfNeeded: createIfNeeded, minVersion: minVersion, cancellationToken: cancellationToken); } public async Task FindCompanionDevice(ILog log, ISimulatorDevice device, CancellationToken cancellationToken = default) { await LoadDevices(log, forceRefresh: false, cancellationToken: cancellationToken); var pair = _availableDevicePairs.Where(v => v.Gizmo == device.UDID).Single(); return _availableDevices.Single(v => v.UDID == pair.Companion); } public async Task<(ISimulatorDevice Simulator, ISimulatorDevice? CompanionSimulator)> FindSimulators( TestTargetOs target, ILog log, int retryCount, bool createIfNeeded = true, bool minVersion = false, CancellationToken cancellationToken = default) { if (retryCount < 1) { throw new ArgumentOutOfRangeException(nameof(retryCount)); } int attempt = 1; while (true) { try { return await FindSimulators(target, log, cancellationToken: cancellationToken); } catch (Exception e) { log.WriteLine($"Failed to find/create simulator (attempt {attempt}/{retryCount}):" + Environment.NewLine + e); if (attempt == retryCount) { throw new NoDeviceFoundException("Failed to find/create suitable simulator", e); } } finally { attempt++; } } } public IEnumerable SelectDevices(TestTarget target, ILog log, bool minVersion, CancellationToken cancellationToken = default) => new SimulatorEnumerable(this, target, minVersion, log, cancellationToken); public IEnumerable SelectDevices(TestTargetOs target, ILog log, bool minVersion, CancellationToken cancellationToken = default) => new SimulatorEnumerable(this, target, minVersion, log, cancellationToken); private class SimulatorXmlNodeComparer : IEqualityComparer { public bool Equals(XmlNode? a, XmlNode? b) { if (a == null) { return b == null; } if (b == null) { return a == null; } return a["Gizmo"].InnerText == b["Gizmo"].InnerText && a["Companion"].InnerText == b["Companion"].InnerText; } public int GetHashCode(XmlNode? node) { if (node == null) { return 0; } return node["Gizmo"].InnerText.GetHashCode() ^ node["Companion"].InnerText.GetHashCode(); } } /// /// SimulatorLoader only finds 2 devices - the main simulator and an optional companion device. /// For backwards compatibility of this library with Xamarin Mac/iOS, we need to be able to enumerate them, hence this class. /// private class SimulatorEnumerable : IEnumerable, IAsyncEnumerable { private readonly Lazy> _findTask; private readonly string _toString; public SimulatorEnumerable(ISimulatorLoader simulators, TestTarget target, bool minVersion, ILog log, CancellationToken cancellationToken = default) { _findTask = new Lazy>( () => simulators.FindSimulators(target, log, minVersion: minVersion, cancellationToken: cancellationToken), LazyThreadSafetyMode.ExecutionAndPublication); _toString = $"Simulators for {target} (MinVersion: {minVersion})"; } public SimulatorEnumerable(ISimulatorLoader simulators, TestTargetOs target, bool minVersion, ILog log, CancellationToken cancellationToken = default) { _findTask = new Lazy>( () => simulators.FindSimulators(target, log, minVersion: minVersion, cancellationToken: cancellationToken), LazyThreadSafetyMode.ExecutionAndPublication); _toString = $"Simulators for {target} (MinVersion: {minVersion})"; } public override string ToString() => _toString; public IEnumerator GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public Task ReadyTask => _findTask.Value; public Task<(ISimulatorDevice, ISimulatorDevice?)> Find() => _findTask.Value; private class Enumerator : IEnumerator { private readonly Lazy<(ISimulatorDevice, ISimulatorDevice?)> _devices; public Enumerator(SimulatorEnumerable enumerable) { _devices = new Lazy<(ISimulatorDevice, ISimulatorDevice?)>(() => enumerable.Find().Result, LazyThreadSafetyMode.ExecutionAndPublication); } private bool? _moved; public ISimulatorDevice Current { get { if (_moved == null) { throw new InvalidOperationException("Call MoveNext() first!"); } return _moved.Value ? _devices.Value.Item2! : _devices.Value.Item1; } } object IEnumerator.Current => Current ?? throw new NullReferenceException("Simulator device not found"); public bool MoveNext() { if (_moved == null) { _moved = false; return _devices.Value.Item1 != null; } if (_moved.Value) { return false; } _moved = true; return _devices.Value.Item2 != null; } public void Reset() => _moved = false; public void Dispose() { } } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/SimulatorSelector.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable enable using System; using System.Collections.Generic; using System.Linq; namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public interface ISimulatorSelector { string GetRuntimePrefix(TestTargetOs target); string GetDeviceType(TestTargetOs target, bool minVersion); void GetCompanionRuntimeAndDeviceType(TestTargetOs target, bool minVersion, out string? companionRuntime, out string? companionDeviceType); ISimulatorDevice SelectSimulator(IEnumerable simulators); } public class DefaultSimulatorSelector : ISimulatorSelector { public virtual string GetRuntimePrefix(TestTargetOs target) { return target.Platform switch { TestTarget.Simulator_iOS64 => "com.apple.CoreSimulator.SimRuntime.iOS-", TestTarget.Simulator_tvOS => "com.apple.CoreSimulator.SimRuntime.tvOS-", TestTarget.Simulator_watchOS => "com.apple.CoreSimulator.SimRuntime.watchOS-", TestTarget.Simulator_xrOS => "com.apple.CoreSimulator.SimRuntime.xrOS-", _ => throw new Exception(string.Format("Invalid simulator target: {0}", target)) }; } public virtual string GetDeviceType(TestTargetOs target, bool minVersion) { return target.Platform switch { TestTarget.Simulator_iOS64 => "com.apple.CoreSimulator.SimDeviceType." + (minVersion ? "iPhone-6s" : "iPhone-11-Pro"), TestTarget.Simulator_tvOS => "com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p", TestTarget.Simulator_watchOS => "com.apple.CoreSimulator.SimDeviceType." + (minVersion ? "Apple-Watch-38mm" : "Apple-Watch-Series-5-40mm"), TestTarget.Simulator_xrOS => "com.apple.CoreSimulator.SimDeviceType.Apple-Vision-Pro", _ => throw new Exception(string.Format("Invalid simulator target: {0}", target)) }; } public virtual void GetCompanionRuntimeAndDeviceType(TestTargetOs target, bool minVersion, out string? companionRuntime, out string? companionDeviceType) { if (target.Platform == TestTarget.Simulator_watchOS) { companionRuntime = "com.apple.CoreSimulator.SimRuntime.iOS-" + (minVersion ? SdkVersions.MinWatchOSCompanionSimulator : SdkVersions.MaxWatchOSCompanionSimulator).Replace('.', '-'); companionDeviceType = "com.apple.CoreSimulator.SimDeviceType." + (minVersion ? "iPhone-6s" : "iPhone-11-Pro"); } else { companionRuntime = null; companionDeviceType = null; } } public ISimulatorDevice SelectSimulator(IEnumerable simulators) { // Put Booted/Booting in front of Shutdown/Unknown return simulators.OrderByDescending(s => s.State).First(); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Hardware/TCCDatabase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; namespace Microsoft.DotNet.XHarness.iOS.Shared.Hardware; public interface ITCCDatabase { Task AgreeToPromptsAsync(string simRuntime, string dataPath, string udid, ILog log, params string[] bundle_identifiers); int GetTCCFormat(string simRuntime); } public class TCCDatabase : ITCCDatabase { private const string IOSSimRuntimePrefix = "com.apple.CoreSimulator.SimRuntime.iOS-"; private const string TvOSSimRuntimePrefix = "com.apple.CoreSimulator.SimRuntime.tvOS-"; private const string WatchOSRuntimePrefix = "com.apple.CoreSimulator.SimRuntime.watchOS-"; private const string xrOSRuntimePrefix = "com.apple.CoreSimulator.SimRuntime.xrOS-"; private readonly IMlaunchProcessManager _processManager; public TCCDatabase(IMlaunchProcessManager processManager) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); } public int GetTCCFormat(string simRuntime) { // v1: < iOS 9 // v2: >= iOS 9 && < iOS 12 // v3: >= iOS 12 // v4: >= iOS 14 if (simRuntime.StartsWith(IOSSimRuntimePrefix, StringComparison.Ordinal)) { var v = Version.Parse(simRuntime.Substring(IOSSimRuntimePrefix.Length).Replace('-', '.')); return v.Major switch { >= 17 => 5, >= 14 => 4, >= 12 => 3, >= 9 => 2, _ => 1 }; } if (simRuntime.StartsWith(TvOSSimRuntimePrefix, StringComparison.Ordinal)) { var v = Version.Parse(simRuntime.Substring(TvOSSimRuntimePrefix.Length).Replace('-', '.')); return v.Major switch { >= 17 => 5, >= 14 => 4, >= 12 => 3, _ => 2 }; } if (simRuntime.StartsWith(WatchOSRuntimePrefix, StringComparison.Ordinal)) { var v = Version.Parse(simRuntime.Substring(WatchOSRuntimePrefix.Length).Replace('-', '.')); return v.Major switch { >= 10 => 5, >= 7 => 4, >= 5 => 3, _ => 2 }; } if (simRuntime.StartsWith(xrOSRuntimePrefix, StringComparison.Ordinal)) { return 5; } throw new NotImplementedException(); } public async Task AgreeToPromptsAsync(string simRuntime, string TCCDb, string udid, ILog log, params string[] bundleIdentifiers) { if (bundleIdentifiers == null || bundleIdentifiers.Length == 0) { log.WriteLine("No bundle identifiers given when requested permission editing."); return false; } var sim_services = new string[] { "kTCCServiceAll", // You'd think 'All' means all prompts, but some prompts still show up. "kTCCServiceAddressBook", "kTCCServiceCalendar", "kTCCServiceCamera", "kTCCServicePhotos", "kTCCServiceMediaLibrary", "kTCCServiceMicrophone", "kTCCServiceUbiquity", "kTCCServiceWillow" }; var failure = false; var tcc_edit_timeout = 3; var watch = new Stopwatch(); watch.Start(); var format = GetTCCFormat(simRuntime); if (format >= 4) { // the following was added due to a bug in Xcode 15 beta 4 and later in which the simulator will // not honor the permissions given by the simctl tool. The issue is as follows, when the permission is // granted via the dialog we find the following record in the TCC.db: // // kTCCServiceAddressBook|com.xamarin.monotouch-test|0|2|2 // // while when we use the simtcl tool, we have the following: // // kTCCServiceAddressBook|com.manuel.test|0|2|4|1|0|UNUSED0|1689713989|||UNUSED|1689713989 // // you can tell that the difference is in the auth_reason, where 2 means the dialog and 4 means the // simtcl tool. The bug is probably in the iOS SDK that is not recognizing the 4 reason. To fix that // we create a trigger in the simulator tcc db that will update the value from 4 to 2 for each inserted row // where 4 was used. if (format >= 5) { var args = new List(); var sql = new System.Text.StringBuilder("\n"); args.Add(TCCDb); sql.AppendFormat("CREATE TRIGGER auth_method_update AFTER INSERT ON access FOR " + "EACH ROW WHEN new.auth_reason = 4 BEGIN " + "UPDATE access SET auth_reason = 2 WHERE client=new.client;" + "END;"); var rv = await _processManager.ExecuteCommandAsync("sqlite3", args, log, TimeSpan.FromSeconds(60)); if (!rv.Succeeded) { // print a warning, but do not set it to failure and hope that the simctl privacy command will work log.WriteLine("Failed to create trigger on TCC.db, some tests might timeout."); } } // We don't care if booting fails (it'll fail if it's already booted for instance) await _processManager.ExecuteXcodeCommandAsync("simctl", new[] { "boot", udid }, log, TimeSpan.FromMinutes(1)); // execute 'simctl privacy grant all ' for each bundle identifier foreach (var bundle_identifier in bundleIdentifiers) { foreach (var bundle_id in new[] { bundle_identifier, bundle_identifier + ".watchkitapp" }) { foreach (var service in sim_services) { var args = new List { "privacy", udid, "grant", service, bundle_id }; var rv = await _processManager.ExecuteXcodeCommandAsync("simctl", args, log, TimeSpan.FromSeconds(30)); if (!rv.Succeeded) { failure = true; break; } } } if (failure) { break; } } } else { do { if (failure) { log.WriteLine("Failed to edit TCC.db, trying again in 1 second... ", (int)(tcc_edit_timeout - watch.Elapsed.TotalSeconds)); await Task.Delay(TimeSpan.FromSeconds(1)); } failure = false; foreach (var bundle_identifier in bundleIdentifiers) { var args = new List(); var sql = new System.Text.StringBuilder("\n"); args.Add(TCCDb); foreach (var bundle_id in new[] { bundle_identifier, bundle_identifier + ".watchkitapp" }) { foreach (var service in sim_services) { switch (format) { case 1: // CREATE TABLE access (service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed INTEGER NOT NULL, prompt_count INTEGER NOT NULL, csreq BLOB, CONSTRAINT key PRIMARY KEY (service, client, client_type)); sql.AppendFormat("DELETE FROM access WHERE service = '{0}' AND client = '{1}';\n", service, bundle_id); sql.AppendFormat("INSERT INTO access VALUES('{0}','{1}',0,1,0,NULL);\n", service, bundle_id); break; case 2: // CREATE TABLE access (service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed INTEGER NOT NULL, prompt_count INTEGER NOT NULL, csreq BLOB, policy_id INTEGER, PRIMARY KEY (service, client, client_type), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE); sql.AppendFormat("DELETE FROM access WHERE service = '{0}' AND client = '{1}';\n", service, bundle_id); sql.AppendFormat("INSERT INTO access VALUES('{0}','{1}',0,1,0,NULL,NULL);\n", service, bundle_id); break; case 3: // Xcode 10+ // CREATE TABLE access (service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed INTEGER NOT NULL, prompt_count INTEGER NOT NULL, csreq BLOB, policy_id INTEGER, indirect_object_identifier_type INTEGER, indirect_object_identifier TEXT, indirect_object_code_identity BLOB, flags INTEGER, last_modified INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER)), PRIMARY KEY (service, client, client_type, indirect_object_identifier), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE) sql.AppendFormat("INSERT OR REPLACE INTO access VALUES('{0}','{1}',0,1,0,NULL,NULL,NULL,'UNUSED',NULL,NULL,{2});\n", service, bundle_id, DateTimeOffset.Now.ToUnixTimeSeconds()); break; default: throw new NotImplementedException(); } } } args.Add(sql.ToString()); var rv = await _processManager.ExecuteCommandAsync("sqlite3", args, log, TimeSpan.FromSeconds(5)); if (!rv.Succeeded) { failure = true; break; } } } while (failure && watch.Elapsed.TotalSeconds <= tcc_edit_timeout); } if (failure) { log.WriteLine("Failed to edit TCC.db, the test run might hang due to permission request dialogs"); } else { log.WriteLine("Successfully edited TCC.db"); } log.WriteLine("Current TCC database contents:"); await _processManager.ExecuteCommandAsync("sqlite3", new[] { TCCDb, ".dump" }, log, TimeSpan.FromSeconds(5)); return !failure; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/IErrorKnowledgeBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; using Microsoft.DotNet.XHarness.Common.Logging; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared; /// /// Interface to be implemented to determine if error in the installation, build and execution of the tests /// are known. This class will help users understand better why an error ocurred. /// public interface IErrorKnowledgeBase { /// /// Identifies via the logs if the installation failure is due to a known issue that the user can act upon. /// /// The installation log. /// A string message for the user to understand the reason for the failure. /// True if the failure is due to a known reason, false otherwise. bool IsKnownInstallIssue(IFileBackedLog installLog, [NotNullWhen(true)] out KnownIssue? knownFailureMessage); /// /// Identifies via the logs if the build failure is due to a known issue that the user can act upon. /// /// The build log. /// A string message for the user to understand the reason for the failure. /// True if the failure is due to a known reason, false otherwise. bool IsKnownBuildIssue(IFileBackedLog buildLog, [NotNullWhen(true)] out KnownIssue? knownFailureMessage); /// /// Identifies via the logs if the run failure is due to a known issue that the user can act upon. /// /// The run log. /// A string message for the user to understand the reason for the failure. /// True if the failure is due to a known reason, false otherwise. bool IsKnownTestIssue(IFileBackedLog runLog, [NotNullWhen(true)] out KnownIssue? knownFailureMessage); } public class KnownIssue { /// /// Human readable message that can be presented to the user. /// public string HumanMessage { get; } /// /// Link to an issue where this problem is being handled. /// public string? IssueLink { get; } /// /// Suggested exit code /// public int? SuggestedExitCode { get; } public KnownIssue(string humanMessage, string? issueLink = null, int? suggestedExitCode = null) { HumanMessage = humanMessage; IssueLink = issueLink; SuggestedExitCode = suggestedExitCode; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/IResultFileHandler.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Threading; using System.Threading.Tasks; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared; public interface IResultFileHandler { /// /// Determines whether the result file handler supports the given OS version and simulator status. /// bool IsVersionSupported(string osVersion, bool isSimulator); /// /// Copy the XML results file from the app container (simulator or device) to the host path. /// Task CopyResultsAsync( RunMode runMode, bool isSimulator, string osVersion, string udid, string bundleIdentifier, string hostDestinationPath); /// /// Copy the coverage results file from the app container (simulator or device) to the host path. /// is the relative file name inside the app's Documents directory. /// Returns true if the file was successfully copied. /// Task CopyCoverageResultsAsync( RunMode runMode, bool isSimulator, string osVersion, string udid, string bundleIdentifier, string coverageFileName, string hostDestinationPath); /// /// Copy the latest crash report from the device and dumps its content to the log. /// Task CopyCrashReportAsync( string deviceUdid, string? deviceName, AppBundleInformation appInformation, Common.Logging.ILog outputLog, bool isSimulator); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/IResultParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.IO; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared; /// /// Interface that represents an object that know how to parse results and generate timeout/crash/build errors so /// that CIs like VSTS and helix can parse them. /// public interface IResultParser { /// /// Generates an XML result that will consider to be an error by the CI. Allows to catch errors in cases in which we are not talking about a test /// failure per se but the situation in which the app could not be built, timeout or crashed. /// void GenerateFailure(ILogs logs, string source, string appName, string? variation, string title, string message, string stderrPath, XmlResultJargon jargon); /// /// Generates an XML result that will consider to be an error by the CI. Allows to catch errors in cases in which we are not talking about a test /// failure per se but the situation in which the app could not be built, timeout or crashed. /// void GenerateFailure(ILogs logs, string source, string appName, string? variation, string title, string message, TextReader stderrReader, XmlResultJargon jargon); /// /// Updates given xml result to contain a list of attachments. This is useful for CI to be able to add logs as part of the attachments of a failing test. /// void UpdateMissingData(string source, string destination, string applicationName, IEnumerable attachments); /// /// Ensures that the given path contains a valid xml result and set the type of xml jargon found in the file. /// bool IsValidXml(string path, out XmlResultJargon type); /// /// Ensures that the given path contains a valid xml result and set the type of xml jargon found in the file. /// bool IsValidXml(TextReader stream, out XmlResultJargon type); /// /// Takes an XML file and removes any extra data that makes the test result not to be a pure xml result for the given jargon. /// /// /// void CleanXml(string source, string destination); /// /// Returns the path to be used for the given jargon. /// /// /// /// string GetXmlFilePath(string path, XmlResultJargon xmlType); /// /// Parses the xml of the given jargon and returns a result line with the summary of what was parsed. /// If destination is provided, creates a human readable report. /// /// File that will be read /// Jargon of the source file /// If provided, will contain human readable result /// A result line with the summary of what was parsed and an over all result (string resultLine, bool failed) ParseResults(string source, XmlResultJargon xmlType, string? humanReadableReportDestination = null); /// /// Parses the xml of the given jargon and returns a result line with the summary of what was parsed. /// If destination is provided, creates a human readable report. /// /// File that will be read /// Jargon of the source file /// If provided, will contain human readable result /// A result line with the summary of what was parsed and an over all result (string resultLine, bool failed) ParseResults(string source, XmlResultJargon xmlType, StreamWriter? textWriter = null); /// /// Generates a human readable test report. /// void GenerateTestReport(TextWriter writer, string resultsPath, XmlResultJargon xmlType); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/ITestReporter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.iOS.Shared; /// /// Interface that represents a class that knows how to parse test results. /// public interface ITestReporter : IDisposable { ILog CallbackLog { get; } bool? Success { get; } CancellationToken CancellationToken { get; } void LaunchCallback(Task launchResult); Task CollectSimulatorResult(ProcessExecutionResult runResult); Task CollectDeviceResult(ProcessExecutionResult runResult); Task<(TestExecutingResult ExecutingResult, string ResultMessage)> ParseResult(); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Listeners/SimpleFileListener.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Threading; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.iOS.Shared.Listeners; public class SimpleFileListener : SimpleListener { private readonly bool _xmlOutput; private Thread _processorThread; private bool _cancel; public string Path { get; private set; } public SimpleFileListener(string path, ILog log, IFileBackedLog testLog, bool xmlOutput) : base(log, testLog) { Path = path ?? throw new ArgumentNullException(nameof(path)); _xmlOutput = xmlOutput; } protected override void Stop() { _cancel = true; _processorThread.Join(); _processorThread = null; Finished(true); } public override int InitializeAndGetPort() { _processorThread = new Thread(Processing); return 0; } protected override void Start() => _processorThread.Start(); private void Processing() { Connected("N/A"); using (var fs = new BlockingFileStream(Path, this)) { using (var reader = new StreamReader(fs)) { string line; while ((line = reader.ReadLine()) != null) { TestLog.WriteLine(line); if (line.StartsWith("[Runner executing:", StringComparison.Ordinal)) { Log.WriteLine("Tests have started executing"); } else if (!_xmlOutput && line.StartsWith("Tests run: ", StringComparison.Ordinal)) { Log.WriteLine("Tests have finished executing"); break; } else if (_xmlOutput && line == "") { Log.WriteLine("Tests have finished executing"); break; } } } } TestLog.Flush(); Finished(); } private class BlockingFileStream : FileStream { private readonly SimpleFileListener _listener; private long _lastPosition; public BlockingFileStream(string path, SimpleFileListener listener) : base(path, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite) { _listener = listener; } public override int Read(byte[] array, int offset, int count) { while (_lastPosition == base.Length && !_listener._cancel) { Thread.Sleep(25); } var rv = base.Read(array, offset, count); _lastPosition += rv; return rv; } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Listeners/SimpleHttpListener.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Net; using System.Net.Sockets; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.iOS.Shared.Listeners; public class SimpleHttpListener : SimpleListener { private readonly bool _autoExit; private HttpListener _server; private bool _connected_once; public int Port { get; private set; } public SimpleHttpListener(ILog log, IFileBackedLog testLog, bool autoExit) : base(log, testLog) { _autoExit = autoExit; } public override int InitializeAndGetPort() { _server = new HttpListener(); if (Port != 0) { throw new NotImplementedException(); } // Try and find an unused port int attemptsLeft = 50; var r = new Random((int)DateTime.Now.Ticks); while (attemptsLeft-- > 0) { var newPort = r.Next(49152, 65535); // The suggested range for dynamic ports is 49152-65535 (IANA) _server.Prefixes.Clear(); _server.Prefixes.Add("http://*:" + newPort + "/"); try { _server.Start(); Port = newPort; break; } catch (Exception ex) { Log.WriteLine("Failed to listen on port {0}: {1}", newPort, ex.Message); } } return Port; } protected override void Stop() => _server.Stop(); protected override void Start() { bool processed; try { Log.WriteLine("Test log server listening on: {0}:{1}", Address, Port); do { var context = _server.GetContext(); processed = Processing(context); } while (!_autoExit || !processed); } catch (Exception e) { if (e is not SocketException se || se.SocketErrorCode != SocketError.Interrupted) { Console.WriteLine("[{0}] : {1}", DateTime.Now, e); } } finally { try { _server.Stop(); } finally { Finished(); } } } private bool Processing(HttpListenerContext context) { var finished = false; var request = context.Request; var response = "OK"; var stream = request.InputStream; var data = string.Empty; using (var reader = new StreamReader(stream)) { data = reader.ReadToEnd(); } stream.Close(); switch (request.RawUrl) { case "/Start": if (!_connected_once) { _connected_once = true; Connected(request.RemoteEndPoint.ToString()); } break; case "/Finish": if (!finished) { TestLog.Write(data); TestLog.Flush(); finished = true; } break; default: Log.WriteLine("Unknown upload url: {0}", request.RawUrl); response = "Unknown upload url"; context.Response.StatusCode = (int)HttpStatusCode.NotFound; break; } var buf = System.Text.Encoding.UTF8.GetBytes(response); context.Response.ContentLength64 = buf.Length; context.Response.OutputStream.Write(buf, 0, buf.Length); context.Response.OutputStream.Close(); context.Response.Close(); return finished; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Listeners/SimpleListener.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Listeners; public interface ISimpleListener : IDisposable { Task CompletionTask { get; } Task ConnectedTask { get; } IFileBackedLog TestLog { get; } void Cancel(); int InitializeAndGetPort(); void StartAsync(); Task StopAsync(); } public abstract class SimpleListener : ISimpleListener { private readonly TaskCompletionSource _stopped = new(); private readonly TaskCompletionSource _connected = new(); private string? _remoteAddress = null; public IFileBackedLog TestLog { get; private set; } protected readonly IPAddress Address = IPAddress.Any; protected ILog Log { get; } protected abstract void Start(); protected abstract void Stop(); public Task ConnectedTask => _connected.Task; public abstract int InitializeAndGetPort(); protected SimpleListener(ILog log, IFileBackedLog testLog) { Log = log ?? throw new ArgumentNullException(nameof(log)); TestLog = testLog ?? throw new ArgumentNullException(nameof(testLog)); } protected void Connected(string remote) { // When app is in some crashing state, it might start connecting and disconnecting in a loop until we end up with an infinite log // So logging only when the endpoint would change (which it shouldn't but we want to know) if (_remoteAddress != remote) { _remoteAddress = remote; Log.WriteLine("Connection from {0} saving logs to {1}", remote, TestLog.FullPath); } _connected.TrySetResult(true); } protected void Finished(bool early_termination = false) { if (_stopped.TrySetResult(early_termination)) { if (early_termination) { Log.WriteLine("Tests were terminated before completion"); } else { Log.WriteLine("Tests have finished executing"); } } } public void StartAsync() { var t = new Thread(() => { try { Start(); } catch (Exception e) { Log.WriteLine($"{GetType().Name}: an exception occurred in processing thread: {e}"); } }) { IsBackground = true, }; t.Start(); } public Task StopAsync() { var t = new Thread(() => { try { Stop(); } catch (Exception e) { Log.WriteLine($"{GetType().Name}: an exception occurred in processing thread: {e}"); } }) { IsBackground = true, }; t.Start(); return _stopped.Task; } public bool WaitForCompletion(TimeSpan ts) => _stopped.Task.Wait(ts); public Task CompletionTask => _stopped.Task; public void Cancel() { lock (_connected) { if (!_connected.TrySetCanceled()) { return; } } try { // Wait a second just in case more data arrives if (!_stopped.Task.Wait(TimeSpan.FromSeconds(1))) { Stop(); } } catch (Exception e) { // We might have stopped already, so just ignore any exceptions. Log.WriteLine($"An exception occurred in processing thread: {e.Message}"); } } public virtual void Dispose() { Cancel(); GC.SuppressFinalize(this); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Listeners/SimpleListenerFactory.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.iOS.Shared.Listeners; public enum ListenerTransport { Tcp, Http, File, } public interface ISimpleListenerFactory { (ListenerTransport transport, ISimpleListener listener, string listenerTempFile) Create( RunMode mode, ILog log, IFileBackedLog testLog, bool isSimulator, bool autoExit, bool xmlOutput); ITunnelBore TunnelBore { get; } bool UseTunnel { get; } } public class SimpleListenerFactory : ISimpleListenerFactory { public ITunnelBore TunnelBore { get; private set; } public bool UseTunnel => TunnelBore != null; public SimpleListenerFactory(ITunnelBore tunnelBore = null) => TunnelBore = tunnelBore; // allow it to be null in case we are working with a sim public (ListenerTransport transport, ISimpleListener listener, string listenerTempFile) Create( RunMode mode, ILog log, IFileBackedLog testLog, bool isSimulator, bool autoExit, bool xmlOutput) { string listenerTempFile = null; ISimpleListener listener; ListenerTransport transport; if (mode == RunMode.WatchOS) { transport = isSimulator ? ListenerTransport.File : ListenerTransport.Http; } else { transport = ListenerTransport.Tcp; } switch (transport) { case ListenerTransport.File: listenerTempFile = testLog.FullPath + ".tmp"; listener = new SimpleFileListener(listenerTempFile, log, testLog, xmlOutput); break; case ListenerTransport.Http: listener = new SimpleHttpListener(log, testLog, autoExit); break; case ListenerTransport.Tcp: listener = new SimpleTcpListener(log, testLog, autoExit, UseTunnel); break; default: throw new NotImplementedException("Unknown type of listener"); } return (transport, listener, listenerTempFile); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Listeners/SimpleTcpListener.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Listeners; public class SimpleTcpListener : SimpleListener, ITunnelListener { private readonly TimeSpan _retryPeriod = TimeSpan.FromMilliseconds(100); private readonly TimeSpan _retryPeriodIncreased = TimeSpan.FromMilliseconds(250); private readonly TimeSpan _increaseAfter = TimeSpan.FromSeconds(20); private readonly TimeSpan _timeoutAfter = TimeSpan.FromMinutes(2); private readonly bool _autoExit; private readonly bool _useTcpTunnel = true; private readonly byte[] _buffer = new byte[16 * 1024]; private TcpListener? _server; private TcpClient? _client; public TaskCompletionSource TunnelHoleThrough { get; } = new TaskCompletionSource(); public int Port { get; private set; } public SimpleTcpListener(ILog log, IFileBackedLog testLog, bool autoExit, bool tunnel = false) : base(log, testLog) { _autoExit = autoExit; _useTcpTunnel = tunnel; } protected override void Stop() { _client?.Close(); _client?.Dispose(); // _server?.Stop(); was causing hangs // https://github.com/dotnet/xharness/issues/73 if (_server?.Server?.Connected ?? false) { _server.Server.Shutdown(SocketShutdown.Both); } } public override int InitializeAndGetPort() { if (_useTcpTunnel && Port != 0) { return Port; } _server = new TcpListener(Address, Port); _server.Start(); if (Port == 0) { Port = ((IPEndPoint)_server.LocalEndpoint).Port; } if (_useTcpTunnel) { // close the listener. We have a port. This is not the best // way to find a free port, but there is nothing we can do // better than this. _server.Stop(); } return Port; } private void StartNetworkTcp() { if (_server == null) { throw new InvalidOperationException("Initialize the listener first via " + nameof(this.InitializeAndGetPort)); } bool processed; try { do { Log.WriteLine("Test log server listening on: {0}:{1}", Address, Port); using (_client = _server.AcceptTcpClient()) { _client.ReceiveBufferSize = _buffer.Length; processed = Processing(_client); } } while (!_autoExit || !processed); } catch (Exception e) { if (e is not SocketException se || se.SocketErrorCode != SocketError.Interrupted) { Log.WriteLine("[{0}] : {1}", DateTime.Now, e); } } finally { try { _server.Stop(); } finally { Finished(); } } } private void StartTcpTunnel() { if (!TunnelHoleThrough.Task.Result) { throw new InvalidOperationException("Tcp tunnel could not be initialized."); } if (_server == null) { throw new InvalidOperationException("Initialize the listener first via " + nameof(this.InitializeAndGetPort)); } bool processed; try { var timeout = _retryPeriod; int logCounter = 0; var watch = Stopwatch.StartNew(); const string address = "localhost"; while (true) { try { _client = new TcpClient(address, Port); Log.WriteLine($"Test log server listening on: {address}:{Port}"); var stream = _client.GetStream(); // Let the device know we are ready! var ping = Encoding.UTF8.GetBytes("ping"); stream.Write(ping, 0, ping.Length); break; } catch (SocketException) { // Give up after some time if (watch.Elapsed > _timeoutAfter) { Log.WriteLine($"TCP connection hasn't started in time ({_timeoutAfter:hh\\:mm\\:ss}). Stopped listening."); throw; } // Switch to a 250 ms timeout after 20 seconds if (timeout == _retryPeriod && watch.Elapsed > _increaseAfter) { timeout = _retryPeriodIncreased; Log.WriteLine( $"TCP tunnel still has not connected. " + $"Increasing retry period from {(int)_retryPeriod.TotalMilliseconds} ms " + $"to {(int)_retryPeriodIncreased.TotalMilliseconds} ms"); } else if ((++logCounter) % 100 == 0) { Log.WriteLine($"TCP tunnel still has not connected"); } Thread.Sleep(timeout); } } do { _client.ReceiveBufferSize = _buffer.Length; processed = Processing(_client); } while (!_autoExit || !processed); } catch (Exception e) { if (e is not SocketException se || se.SocketErrorCode != SocketError.Interrupted) { Log.WriteLine($"Failed to read TCP data: {e}"); } } finally { Finished(); } } protected override void Start() { if (_useTcpTunnel) { StartTcpTunnel(); } else { StartNetworkTcp(); } } private bool Processing(TcpClient client) { Connected(client.Client.RemoteEndPoint.ToString()); // now simply copy what we receive int i; int total = 0; NetworkStream stream = client.GetStream(); while ((i = stream.Read(_buffer, 0, _buffer.Length)) != 0) { TestLog.Write(_buffer, 0, i); TestLog.Flush(); total += i; } if (total < 16) { // This wasn't a test run, but a connection from the app (on device) to find // the ip address we're reachable on. return false; } return true; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Listeners/TcpTunnel.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; namespace Microsoft.DotNet.XHarness.iOS.Shared.Listeners; // interface to be implemented by those listeners that can use a tcp tunnel public interface ITunnelListener : ISimpleListener { TaskCompletionSource TunnelHoleThrough { get; } int Port { get; } } // interface implemented by a tcp tunnel between the host and the device. public interface ITcpTunnel : IAsyncDisposable { public void Open(string device, ITunnelListener simpleListener, TimeSpan timeout, ILog mainLog); public Task Close(); public Task Started { get; } } // represents a tunnel created between a device and a host. This tunnel allows communication between // the host and the device via the usb cable. public class TcpTunnel : ITcpTunnel { private readonly object _processExecutionLock = new(); private readonly IMlaunchProcessManager _processManager; private Task _tcpTunnelExecutionTask = null; private CancellationTokenSource _cancellationToken; public TaskCompletionSource startedCompletionSource { get; private set; } = new TaskCompletionSource(); public Task Started => startedCompletionSource.Task; public int Port { get; private set; } public TcpTunnel(IMlaunchProcessManager processManager) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); } public void Open(string device, ITunnelListener simpleListener, TimeSpan timeout, ILog mainLog) { if (device == null) { throw new ArgumentNullException(nameof(device)); } if (simpleListener == null) { throw new ArgumentNullException(nameof(simpleListener)); } if (mainLog == null) { throw new ArgumentNullException(nameof(mainLog)); } lock (_processExecutionLock) { // launch app, but do not await for the result, since we need to create the tunnel var tcpArgs = new MlaunchArguments { new TcpTunnelArgument(simpleListener.Port), new DeviceNameArgument(device), }; // use a cancelation token, later will be used to kill the tcp tunnel process _cancellationToken = new CancellationTokenSource(); mainLog.WriteLine($"Starting TCP tunnel between mac port: {simpleListener.Port} and device port {simpleListener.Port}"); Port = simpleListener.Port; var tunnelbackLog = new CallbackLog((line) => { mainLog.WriteLine($"[TCP tunnel] {line}"); if (line.Contains("Tcp tunnel started on device")) { mainLog.Write($"TCP tunnel created on port {simpleListener.Port}"); startedCompletionSource.TrySetResult(true); simpleListener.TunnelHoleThrough.TrySetResult(true); } }); // do not await since we are going to be running the process in parallel _tcpTunnelExecutionTask = _processManager.ExecuteCommandAsync(tcpArgs, tunnelbackLog, timeout, cancellationToken: _cancellationToken.Token); _tcpTunnelExecutionTask.ContinueWith(delegate (Task task) { // if the task completes, means that we had issues with the creation of the tunnel and the process // exited, if that is the case, we do not want to make the app wait, therefore, set the hole to false // which will throw an exception from the listener. simpleListener.TunnelHoleThrough.TrySetResult(task.Result.Succeeded); }); } } public async Task Close() { if (_cancellationToken == null) { throw new InvalidOperationException("Cannot close tunnel that was not opened."); } // cancel process and wait for it to terminate, else we might want to start a second tunnel to the same device // which is going to give problems. _cancellationToken.Cancel(); await _tcpTunnelExecutionTask; } public async ValueTask DisposeAsync() { await Close(); GC.SuppressFinalize(this); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Listeners/TunnelBore.cs ================================================ using System; using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; namespace Microsoft.DotNet.XHarness.iOS.Shared.Listeners; /// /// manages the tunnels that are used to communicate with the devices. We want to create a single tunnel per device /// since we can only run one app per device, this should not be a problem. /// /// definition: /// A tunnel boring machine, also known as a "mole", is a machine used to excavate tunnels with a circular cross section /// through a variety of soil and rock strata. They may also be used for microtunneling. /// public interface ITunnelBore : IAsyncDisposable { // create a new tunnel for the device with the given name. ITcpTunnel Create(string device, ILog mainLog); // close a given tunnel Task Close(string device); } public class TunnelBore : ITunnelBore { private readonly object _tunnelsLock = new(); private readonly IMlaunchProcessManager _processManager; private readonly ConcurrentDictionary _tunnels = new(); public TunnelBore(IMlaunchProcessManager processManager) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); } // Creates a new tcp tunnel to the given device that will use the port from the passed listener. public ITcpTunnel Create(string device, ILog mainLog) { lock (_tunnelsLock) { if (_tunnels.ContainsKey(device)) { string msg = $"Cannot create more than one tunnel to device {device}"; mainLog.WriteLine(msg); throw new InvalidOperationException(msg); } _tunnels[device] = new TcpTunnel(_processManager); return _tunnels[device]; } } public async Task Close(string device) { // closes a tcp tunnel that was created for the given device. if (_tunnels.TryRemove(device, out var tunnel)) { await tunnel.DisposeAsync(); // calls close already } } public async ValueTask DisposeAsync() { var devices = _tunnels.Keys.ToArray(); foreach (var d in devices) { if (_tunnels.TryRemove(d, out var tunnel)) { // blocking, but we are disposing await tunnel.DisposeAsync(); // alls close already } } GC.SuppressFinalize(this); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Logging/AppInstallMonitorLog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.iOS.Shared.Logging; // Monitor the output from 'mlaunch --installdev' and cancel the installation if there's no output for 1 minute. public class AppInstallMonitorLog : FileBackedLog { private readonly IFileBackedLog _copyTo; private readonly CancellationTokenSource _cancellationSource; public override string FullPath => _copyTo.FullPath; public CancellationToken CancellationToken => _cancellationSource.Token; public bool CopyingApp; public bool CopyingWatchApp; public TimeSpan AppCopyDuration; public TimeSpan WatchAppCopyDuration; public Stopwatch AppCopyStart = new(); public Stopwatch WatchAppCopyStart = new(); public int AppPercentComplete; public int WatchAppPercentComplete; public long AppBytes; public long WatchAppBytes; public long AppTotalBytes; public long WatchAppTotalBytes; public AppInstallMonitorLog(IFileBackedLog copy_to) : base($"Watch transfer log for {copy_to.Description}") { _copyTo = copy_to; _cancellationSource = new CancellationTokenSource(); _cancellationSource.Token.Register(() => { copy_to.WriteLine("App installation cancelled: it timed out after no output for 1 minute."); }); } public override Encoding Encoding => _copyTo.Encoding; public override void Flush() => _copyTo.Flush(); public override StreamReader GetReader() => _copyTo.GetReader(); public override void Dispose() { _copyTo.Dispose(); _cancellationSource.Dispose(); GC.SuppressFinalize(this); } private void ResetTimer() => _cancellationSource.CancelAfter(TimeSpan.FromMinutes(1)); protected override void WriteImpl(string? value) { var v = value?.Trim() ?? string.Empty; if (v.StartsWith("Installing application bundle", StringComparison.Ordinal)) { if (!CopyingApp) { CopyingApp = true; AppCopyStart.Start(); } else if (!CopyingWatchApp) { CopyingApp = false; CopyingWatchApp = true; AppCopyStart.Stop(); WatchAppCopyStart.Start(); } } else if (v.StartsWith("PercentComplete: ", StringComparison.Ordinal) && int.TryParse(v.Substring("PercentComplete: ".Length).Trim(), out var percent)) { if (CopyingApp) { AppPercentComplete = percent; } else if (CopyingWatchApp) { WatchAppPercentComplete = percent; } } else if (v.StartsWith("NumBytes: ", StringComparison.Ordinal) && int.TryParse(v.Substring("NumBytes: ".Length).Trim(), out var num_bytes)) { if (CopyingApp) { AppBytes = num_bytes; AppCopyDuration = AppCopyStart.Elapsed; } else if (CopyingWatchApp) { WatchAppBytes = num_bytes; WatchAppCopyDuration = WatchAppCopyStart.Elapsed; } } else if (v.StartsWith("TotalBytes: ", StringComparison.Ordinal) && int.TryParse(v.Substring("TotalBytes: ".Length).Trim(), out var total_bytes)) { if (CopyingApp) { AppTotalBytes = total_bytes; } else if (CopyingWatchApp) { WatchAppTotalBytes = total_bytes; } } ResetTimer(); _copyTo.WriteLine(value); } public override void Write(byte[] buffer, int offset, int count) => _copyTo.Write(buffer, offset, count); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Logging/CaptureLog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Threading; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.iOS.Shared.Logging; public interface ICaptureLogFactory { ICaptureLog Create(string path, string systemLogPath, bool entireFile, string description = null); ICaptureLog Create(string path, string systemLogPath, bool entireFile, LogType logType); } public class CaptureLogFactory : ICaptureLogFactory { public ICaptureLog Create(string path, string systemLogPath, bool entireFile, string description = null) => new CaptureLog(path, systemLogPath, entireFile) { Description = description }; public ICaptureLog Create(string path, string systemLogPath, bool entireFile, LogType logType) => Create(path, systemLogPath, entireFile, logType.ToString()); } public interface ICaptureLog : IFileBackedLog { void StartCapture(); void StopCapture(TimeSpan? waitIfEmpty = null); } /// /// A log that captures data written to a separate file between two moments in time /// (between StartCapture and StopCapture). /// public class CaptureLog : FileBackedLog, ICaptureLog { private readonly bool _entireFile; private long _startPosition; private long _endPosition; private bool _started; private bool _stopped; /// /// File we are watching /// public string CapturePath { get; } /// /// Destination file we are copying to /// public override string FullPath { get; } public CaptureLog(string destinationPath, string capturedPath, bool entireFile = false) { FullPath = destinationPath ?? throw new ArgumentNullException(nameof(destinationPath)); CapturePath = capturedPath ?? throw new ArgumentNullException(nameof(destinationPath)); _entireFile = entireFile; } public void StartCapture() { if (_entireFile) { return; } if (File.Exists(CapturePath)) { _startPosition = new FileInfo(CapturePath).Length; } _started = true; } public void StopCapture(TimeSpan? waitIfEmpty = null) { if (_stopped) { return; } lock (CapturePath) { if (_stopped) { return; } try { if (!_started && !_entireFile) { throw new InvalidOperationException("StartCapture() must be called before StopCature() on when the entire file is being captured."); } if (!File.Exists(CapturePath)) { File.WriteAllText(FullPath, $"Could not capture the file '{CapturePath}' because it doesn't exist."); return; } _endPosition = new FileInfo(CapturePath).Length; if ((_endPosition == 0 || (_startPosition == _endPosition && !_entireFile)) && waitIfEmpty.HasValue) { Thread.Sleep((int)waitIfEmpty.Value.TotalMilliseconds); _endPosition = new FileInfo(CapturePath).Length; } if (_entireFile) { File.Copy(CapturePath, FullPath, true); return; } Capture(); } finally { _stopped = true; } } } private void Capture() { if (_entireFile) { return; } if (!File.Exists(CapturePath)) { File.AppendAllText(FullPath, $"{Environment.NewLine}Could not capture the file '{CapturePath}' because it does not exist."); return; } var sourceLength = new FileInfo(CapturePath).Length; var endPosition = _endPosition; if (endPosition == 0) { endPosition = sourceLength; } // The file shrank? lets copy the entire file in this case, which is better than nothing if (endPosition < _startPosition) { File.Copy(CapturePath, FullPath, true); return; } var destFile = new FileInfo(FullPath); var alreadyCapturedLength = destFile.Exists ? destFile.Length : 0L; if (alreadyCapturedLength + _startPosition >= sourceLength) { // We've captured before, and nothing new as added since last time. return; } var readPosition = _startPosition + alreadyCapturedLength; using var reader = new FileStream(CapturePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using var writer = new FileStream(FullPath, FileMode.Create, FileAccess.Write, FileShare.Read); var buffer = new byte[4096]; reader.Seek(readPosition, SeekOrigin.Begin); while (readPosition < endPosition) { int bytesRead = reader.Read(buffer, 0, (int)Math.Min(buffer.Length, endPosition - readPosition)); if (bytesRead > 0) { writer.Write(buffer, 0, bytesRead); readPosition += bytesRead; } else { // There's nothing more to read. // I can't see how we get here, since we calculate the amount to read based on what's available, but it does happen randomly. break; } } } public override StreamReader GetReader() { lock (CapturePath) { // If we copied the original file over, use it as the source for reading // (original file might not exist anymore) if (_stopped) { return new StreamReader(new FileStream(FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); } } // If we want to capture something in the future that doesn't exist yet if (!File.Exists(CapturePath)) { return null; } var stream = new FileStream(CapturePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // Find the spot where we started capturing stream.Seek(_startPosition, SeekOrigin.Begin); return new StreamReader(stream); } public override void Flush() => Capture(); protected override void WriteImpl(string? value) => throw new InvalidOperationException(); public override void Dispose() { StopCapture(); GC.SuppressFinalize(this); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Logging/DeviceLogCapturer.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.IO; using System.Text; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; namespace Microsoft.DotNet.XHarness.iOS.Shared.Logging; public interface IDeviceLogCapturer : IDisposable { void StartCapture(); void StopCapture(); } public class DeviceLogCapturer : IDeviceLogCapturer { private readonly ILog _mainLog; private readonly ILog _deviceLog; private readonly string _deviceUdid; private readonly string _outputPath; private DateTime _startTime; public DeviceLogCapturer(ILog mainLog, ILog deviceLog, string deviceUdid) { _mainLog = mainLog ?? throw new ArgumentNullException(nameof(mainLog)); _deviceLog = deviceLog ?? throw new ArgumentNullException(nameof(deviceLog)); _deviceUdid = deviceUdid ?? throw new ArgumentNullException(nameof(deviceUdid)); _outputPath = Path.Combine(Path.GetTempPath(), $"device_logs_{Guid.NewGuid()}.logarchive"); } public void StartCapture() { _startTime = DateTime.Now; _deviceLog.WriteLine($"Device log capture started at {_startTime:yyyy-MM-dd HH:mm:ss}"); } public void StopCapture() { _deviceLog.WriteLine($"Device log capture stopped at {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); string startTimeStr = _startTime.ToString("yyyy-MM-dd HH:mm:ss"); // Collect logs. Use a timeout to avoid hanging indefinitely if the device // becomes unresponsive (e.g. tvOS devices with broken log streaming). const int processTimeoutMs = 120_000; // 2 minutes string collectArguments = $"log collect --device-udid {_deviceUdid} --start \"{startTimeStr}\" --output \"{_outputPath}\""; _deviceLog.WriteLine($"Collecting logs: sudo {collectArguments}"); using Process collectProcess = new Process(); collectProcess.StartInfo.FileName = "sudo"; collectProcess.StartInfo.Arguments = collectArguments; collectProcess.StartInfo.UseShellExecute = false; collectProcess.StartInfo.RedirectStandardOutput = true; collectProcess.StartInfo.RedirectStandardError = true; StringBuilder collectOutput = new StringBuilder(); StringBuilder collectErrors = new StringBuilder(); collectProcess.OutputDataReceived += (sender, e) => { if (e.Data != null) collectOutput.AppendLine(e.Data); }; collectProcess.ErrorDataReceived += (sender, e) => { if (e.Data != null) collectErrors.AppendLine(e.Data); }; collectProcess.Start(); collectProcess.BeginOutputReadLine(); collectProcess.BeginErrorReadLine(); if (!collectProcess.WaitForExit(processTimeoutMs)) { _mainLog.WriteLine($"Device log collection timed out after {processTimeoutMs / 1000}s. Killing process and skipping log reading."); try { collectProcess.Kill(entireProcessTree: true); } catch { /* best effort */ } CleanupOutputPath(); return; } // Ensure all asynchronous output/error reads have completed before consuming buffers collectProcess.WaitForExit(); if (collectErrors.Length > 0) { _mainLog.WriteLine($"Errors during log collection: {collectErrors}"); if (collectProcess.ExitCode != 0) { _deviceLog.WriteLine($"Log collection failed with exit code {collectProcess.ExitCode}. Skipping log reading."); CleanupOutputPath(); return; } } // Read the collected logs string readArguments = $"show \"{_outputPath}\""; _deviceLog.WriteLine($"Reading logs: log {readArguments}"); using Process readProcess = new Process(); readProcess.StartInfo.FileName = "log"; readProcess.StartInfo.Arguments = readArguments; readProcess.StartInfo.UseShellExecute = false; readProcess.StartInfo.RedirectStandardOutput = true; readProcess.StartInfo.RedirectStandardError = true; StringBuilder output = new StringBuilder(); StringBuilder errors = new StringBuilder(); readProcess.OutputDataReceived += (sender, e) => { if (e.Data != null) output.AppendLine(e.Data); }; readProcess.ErrorDataReceived += (sender, e) => { if (e.Data != null) errors.AppendLine(e.Data); }; readProcess.Start(); readProcess.BeginOutputReadLine(); readProcess.BeginErrorReadLine(); if (!readProcess.WaitForExit(processTimeoutMs)) { _mainLog.WriteLine($"Device log reading timed out after {processTimeoutMs / 1000}s. Killing process."); try { readProcess.Kill(entireProcessTree: true); } catch { /* best effort */ } } else { // Ensure all asynchronous output/error reads have completed before consuming buffers readProcess.WaitForExit(); if (output.Length > 0) { lock (_deviceLog) { _deviceLog.WriteLine(output.ToString()); } } if (errors.Length > 0) { _mainLog.WriteLine($"Errors while reading device logs: {errors}"); } } CleanupOutputPath(); } private void CleanupOutputPath() { if (Directory.Exists(_outputPath)) { Directory.Delete(_outputPath, true); } } public void Dispose() => StopCapture(); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Logging/IEventLogger.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.iOS.Shared.Logging; public interface IEventLogger { public void LogEvent(ILog log, string text, params object[] args); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Logging/ILogs.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.iOS.Shared.Logging; public interface ILogs : IList, IDisposable { string Directory { get; set; } // Create a new log backed with a file IFileBackedLog Create(string filename, string description, bool? timestamp = null); // Adds an existing file to this collection of logs. // If the file is not inside the log directory, then it's copied there. // 'path' must be a full path to the file. IFileBackedLog AddFile(string path); // Adds an existing file to this collection of logs. // If the file is not inside the log directory, then it's copied there. // 'path' must be a full path to the file. IFileBackedLog AddFile(string path, string name); // Create an empty file in the log directory and return the full path to the file string CreateFile(string path, string description); string CreateFile(string path, LogType type); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Logging/LogFile.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using Microsoft.DotNet.XHarness.Common.Logging; namespace Microsoft.DotNet.XHarness.iOS.Shared.Logging; public class LogFile : FileBackedLog { private readonly object _lockObj = new(); public override string FullPath { get; } private FileStream _writer; private bool _disposed; public LogFile(string description, string path, bool append = true) : base(description) { FullPath = path ?? throw new ArgumentNullException(nameof(path)); if (!append) { File.WriteAllText(path, string.Empty); } } public override void Write(byte[] buffer, int offset, int count) { try { // We don't want to open the file every time someone writes to the log, so we keep it as an instance // variable until we're disposed. Due to the async nature of how we run tests, writes may still // happen after we're disposed, in which case we create a temporary stream we close after writing lock (_lockObj) { var fs = _writer ?? new FileStream(FullPath, FileMode.Append, FileAccess.Write, FileShare.Read); fs.Write(buffer, offset, count); if (_disposed) { fs.Dispose(); } else { _writer = fs; } } } catch (Exception e) { Console.Error.WriteLine($"Failed to write to the file {FullPath}: {e}."); return; } } public override void Flush() { if (!_disposed) { _writer?.Flush(); } } protected override void WriteImpl(string? value) { var bytes = Encoding.GetBytes(value ?? string.Empty); Write(bytes, 0, bytes.Length); } public override StreamReader GetReader() => new(new FileStream(FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); public override void Dispose() { lock (_lockObj) { if (!_disposed) { _writer?.Flush(); _writer?.Dispose(); _writer = null; } } _disposed = true; GC.SuppressFinalize(this); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Logging/LogType.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.iOS.Shared.Logging; // defines common log types public enum LogType { XmlLog, NUnitResult, SystemLog, CompanionSystemLog, BuildLog, TestLog, ExtensionTestLog, ExecutionLog, TrxLog, HtmlLog, ApplicationLog, } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Logging/Logs.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Microsoft.DotNet.XHarness.iOS.Shared.Logging; public class Logs : List, ILogs { private readonly IHelpers _helpers = new Helpers(); public string Directory { get; set; } public Logs(string directory) { Directory = directory ?? throw new ArgumentNullException(nameof(directory)); } public IFileBackedLog Create(string filename, string description, bool? timestamp = null) { System.IO.Directory.CreateDirectory(Directory); var rv = new LogFile(description, Path.GetFullPath(Path.Combine(Directory, filename))); if (timestamp.HasValue) { rv.Timestamp = timestamp.Value; } Add(rv); return rv; } // Adds an existing file to this collection of logs. // If the file is not inside the log directory, then it's copied there. // 'path' must be a full path to the file. public IFileBackedLog AddFile(string path) => AddFile(path, Path.GetFileName(path)); // Adds an existing file to this collection of logs. // If the file is not inside the log directory, then it's copied there. // 'path' must be a full path to the file. public IFileBackedLog AddFile(string path, string name) { if (path == null) { throw new ArgumentNullException(nameof(path)); } if (!path.StartsWith(Directory, StringComparison.Ordinal)) { var newPath = Path.Combine(Directory, Path.GetFileNameWithoutExtension(path) + "-" + _helpers.Timestamp + Path.GetExtension(path)); File.Copy(path, newPath, true); path = newPath; } var log = new LogFile(name, path, true); Add(log); return log; } // Create an empty file in the log directory and return the full path to the file public string CreateFile(string path, string description) { if (path == null) { throw new ArgumentNullException(nameof(path)); } using (var rv = new LogFile(description, Path.Combine(Directory, path), false)) { Add(rv); return rv.FullPath; } } public string CreateFile(string path, LogType type) => CreateFile(path, type.ToString()); public void Dispose() { foreach (var log in this) { log.Dispose(); } GC.SuppressFinalize(this); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Logging/WrenchLog.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.iOS.Shared.Logging; public static class WrenchLog { public static void WriteLine(string message, params object[] args) => WriteLine(string.Format(message, args)); public static void WriteLine(string message) { // disabled, might be deleted later. } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Microsoft.DotNet.XHarness.iOS.Shared.csproj ================================================  $(XHarnessNetTFMs) true disable $(NoWarn);8600;8601;8602;8603;8604;8632 ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/ResultFileHandler.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared; public class ResultFileHandler : IResultFileHandler { private static readonly int[] DefaultRetryDelaysMs = { 5_000, 10_000, 20_000 }; private IMlaunchProcessManager _processManager; private IFileBackedLog _mainLog; private readonly int[] _retryDelaysMs; public ResultFileHandler(IMlaunchProcessManager pm, IFileBackedLog fs, int[]? retryDelaysMs = null) { _processManager = pm; _mainLog = fs; // Clone to prevent external mutation of the shared default array _retryDelaysMs = (retryDelaysMs ?? DefaultRetryDelaysMs).ToArray(); } public bool IsVersionSupported(string osVersion, bool isSimulator) { if (isSimulator) { // Version format contains string like "Simulator 18.0". string[] osVersionParts = osVersion.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (osVersionParts.Length < 2) { throw new FormatException("Simulator OS version is not in the expected format."); } if (!Version.TryParse(osVersionParts[1], out Version osVersionParsed)) { throw new FormatException("Simulator OS version is not in the expected format."); } if (osVersionParsed.Major >= 18) { return true; } } else { if (!Version.TryParse(osVersion, out Version osVersionParsed)) { throw new FormatException($"Device OS version is not in the expected format."); } if (osVersionParsed.Major >= 18) { return true; } } return false; } public async Task CopyResultsAsync( RunMode runMode, bool isSimulator, string osVersion, string udid, string bundleIdentifier, string hostDestinationPath) { if (!ShouldCopyFromAppContainer(runMode, osVersion, isSimulator)) { return true; } return await CopyFileFromAppContainerAsync( runMode, isSimulator, udid, bundleIdentifier, GetAppContainerSourcePath(runMode, "test-results.xml"), hostDestinationPath, "results file"); } public async Task CopyCoverageResultsAsync( RunMode runMode, bool isSimulator, string osVersion, string udid, string bundleIdentifier, string coverageFileName, string hostDestinationPath) { if (!ShouldCopyFromAppContainer(runMode, osVersion, isSimulator)) { return false; } return await CopyFileFromAppContainerAsync( runMode, isSimulator, udid, bundleIdentifier, GetAppContainerSourcePath(runMode, coverageFileName), hostDestinationPath, "coverage results file"); } private bool ShouldCopyFromAppContainer(RunMode runMode, string osVersion, bool isSimulator) => runMode == RunMode.MacOS || IsVersionSupported(osVersion, isSimulator); private static string GetAppContainerSourcePath(RunMode runMode, string fileName) => runMode is RunMode.iOS or RunMode.MacOS ? $"/Documents/{fileName}" : $"/Library/Caches/Documents/{fileName}"; private async Task CopyFileFromAppContainerAsync( RunMode runMode, bool isSimulator, string udid, string bundleIdentifier, string sourcePath, string hostDestinationPath, string fileDescription) { string copySourceDescription; string cmd; if (runMode == RunMode.MacOS) { string containerPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Containers", bundleIdentifier, "Data"); copySourceDescription = "MacCatalyst app container"; cmd = $"cp \"{containerPath}{sourcePath}\" \"{hostDestinationPath}\""; } else if (isSimulator) { copySourceDescription = "simulator"; cmd = $"cp \"$(xcrun simctl get_app_container {udid} {bundleIdentifier} data){sourcePath}\" \"{hostDestinationPath}\""; } else { copySourceDescription = "device"; cmd = $"xcrun devicectl device copy from --device {udid} --source {sourcePath} --destination {hostDestinationPath} --user mobile --domain-type appDataContainer --domain-identifier {bundleIdentifier}"; } // Retry up to 3 times with increasing delays to handle transient device communication errors // (e.g., com.apple.Mercury.error 1000 or RSD error 0xE8000003 on tvOS devices). for (int attempt = 0; attempt <= _retryDelaysMs.Length; attempt++) { if (attempt > 0) { int delayMs = _retryDelaysMs[attempt - 1]; _mainLog.WriteLine($"Retrying {fileDescription} copy (attempt {attempt + 1}) after {delayMs / 1000}s delay..."); await Task.Delay(delayMs); // Remove a partial/failed destination file before retrying if (File.Exists(hostDestinationPath)) { File.Delete(hostDestinationPath); } } await _processManager.ExecuteCommandAsync( "/bin/bash", new[] { "-c", cmd }, _mainLog, _mainLog, _mainLog, TimeSpan.FromMinutes(1), null); if (File.Exists(hostDestinationPath)) { return true; } _mainLog.WriteLine($"Failed to copy {fileDescription} from {copySourceDescription} (attempt {attempt + 1}). Expected at: {hostDestinationPath}"); } return false; } public async Task CopyCrashReportAsync( string deviceUdid, string? deviceName, AppBundleInformation appInformation, ILog outputLog, bool isSimulator) { _mainLog.WriteLine("Attempting to retrieve crash report from device..."); // List all crash reports on the device string tempCrashListFile = Path.GetTempFileName(); MlaunchArguments listArgs = new MlaunchArguments(new ListCrashReportsArgument(tempCrashListFile)); if (!string.IsNullOrEmpty(deviceName)) { listArgs.Add(new DeviceNameArgument(deviceName)); } ProcessExecutionResult listResult = await _processManager.ExecuteCommandAsync( listArgs, _mainLog, TimeSpan.FromMinutes(1)); if (!listResult.Succeeded || !File.Exists(tempCrashListFile)) { _mainLog.WriteLine("Failed to list crash reports from device."); return; } List crashReports = File.ReadAllLines(tempCrashListFile) .Where(line => !string.IsNullOrWhiteSpace(line)) .ToList(); if (crashReports.Count == 0) { _mainLog.WriteLine("No crash reports found on device."); return; } // Filter for crash reports that might be related to our app // .ips files typically follow the format: AppName-YYYY-MM-DD-HHMMSS.ips or similar List appRelatedCrashes = crashReports .Where(crash => crash.Contains(appInformation.AppName, StringComparison.OrdinalIgnoreCase) || crash.EndsWith(".ips", StringComparison.OrdinalIgnoreCase)) .ToList(); // Use the last crash report (most recent) from the filtered list string latestCrashReport = (appRelatedCrashes.Count > 0 ? appRelatedCrashes : crashReports).Last(); _mainLog.WriteLine($"Found crash report: {latestCrashReport}"); // Download the crash report string? uploadRoot = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT"); string crashReportContent; if (!string.IsNullOrEmpty(uploadRoot) && Directory.Exists(uploadRoot)) { string crashFileName = Path.GetFileName(latestCrashReport); crashReportContent = Path.Combine(uploadRoot, crashFileName); } else { crashReportContent = Path.GetTempFileName(); } MlaunchArguments downloadArgs = new MlaunchArguments( new DownloadCrashReportArgument(latestCrashReport), new DownloadCrashReportToArgument(crashReportContent)); if (!string.IsNullOrEmpty(deviceName)) { downloadArgs.Add(new DeviceNameArgument(deviceName)); } ProcessExecutionResult downloadResult = await _processManager.ExecuteCommandAsync( downloadArgs, _mainLog, TimeSpan.FromMinutes(1)); if (!downloadResult.Succeeded || !File.Exists(crashReportContent)) { _mainLog.WriteLine("Failed to download crash report from device."); return; } // Dump the crash report content to the log _mainLog.WriteLine($"==================== Crash report ===================="); _mainLog.WriteLine($"Crash report file: {crashReportContent}"); string crashContent = await File.ReadAllTextAsync(crashReportContent); _mainLog.WriteLine(crashContent); _mainLog.WriteLine($"==================== End of Crash report ===================="); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/RunMode.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace Microsoft.DotNet.XHarness.iOS.Shared; public enum RunMode { iOS, TvOS, WatchOS, xrOS, MacOS, } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/SdkVersions.cs ================================================ namespace Microsoft.DotNet.XHarness.iOS.Shared; /// /// This file decides what environment will XHarness expect and what Simulators to be preinstalled. /// public static class SdkVersions { public static string Xcode { get; private set; } = "14.3"; public static string OSX { get; private set; } = "13.00"; public static string iOS { get; private set; } = "16.4"; public static string WatchOS { get; private set; } = "6.2"; public static string TVOS { get; private set; } = "16.4"; public static string MinOSX { get; private set; } = "10.9"; public static string MiniOS { get; private set; } = "7.0"; public static string MinWatchOS { get; private set; } = "2.0"; public static string MinTVOS { get; private set; } = "9.0"; public static string MiniOSSimulator { get; private set; } = "10.3"; public static string MinWatchOSSimulator { get; private set; } = "3.2"; public static string MinWatchOSCompanionSimulator { get; private set; } = "10.3"; public static string MinTVOSSimulator { get; private set; } = "10.2"; public static string MaxiOSSimulator { get; private set; } = "16.4"; public static string MaxWatchOSSimulator { get; private set; } = "8.0"; public static string MaxWatchOSCompanionSimulator { get; private set; } = "16.4"; public static string MaxTVOSSimulator { get; private set; } = "16.4"; public static string MaxiOSDeploymentTarget { get; private set; } = "16.4"; public static string MaxWatchDeploymentTarget { get; private set; } = "8.0"; public static string MaxTVOSDeploymentTarget { get; private set; } = "16.4"; public static string MinxrOSSimulator { get; private set; } = "1.0"; public static string MaxxrOSSimulator { get; private set; } = "1.0"; public static void OverrideVersions(string xcode, string osx, string iOS, string watchOS, string tVOS, string minOSX, string miniOS, string minWatchOS, string minTVOS, string miniOSSimulator, string minWatchOSSimulator, string minWatchOSCompanionSimulator, string minTVOSSimulator, string maxiOSSimulator, string maxWatchOSSimulator, string maxWatchOSCompanionSimulator, string maxTVOSSimulator, string maxiOSDeploymentTarget, string maxWatchDeploymentTarget, string maxTVOSDeploymentTarget, string minxrOSSimulator = "1.0", string maxxrOSSimulator = "1.0") { Xcode = xcode; OSX = osx; SdkVersions.iOS = iOS; WatchOS = watchOS; TVOS = tVOS; MinOSX = minOSX; MiniOS = miniOS; MinWatchOS = minWatchOS; MinTVOS = minTVOS; MiniOSSimulator = miniOSSimulator; MinWatchOSSimulator = minWatchOSSimulator; MinWatchOSCompanionSimulator = minWatchOSCompanionSimulator; MinTVOSSimulator = minTVOSSimulator; MaxiOSSimulator = maxiOSSimulator; MaxWatchOSSimulator = maxWatchOSSimulator; MaxWatchOSCompanionSimulator = maxWatchOSCompanionSimulator; MaxTVOSSimulator = maxTVOSSimulator; MaxiOSDeploymentTarget = maxiOSDeploymentTarget; MaxWatchDeploymentTarget = maxWatchDeploymentTarget; MaxTVOSDeploymentTarget = maxTVOSDeploymentTarget; MinxrOSSimulator = minxrOSSimulator; MaxxrOSSimulator = maxxrOSSimulator; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/TestExecutingResult.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; namespace Microsoft.DotNet.XHarness.iOS.Shared; [Flags] public enum TestExecutingResult { NotStarted = 0, InProgress = 0x1, Finished = 0x2, Waiting = 0x4, StateMask = NotStarted | InProgress | Waiting | Finished, // In progress state Building = 0x10 | InProgress, BuildQueued = 0x10 | InProgress | Waiting, Built = 0x20 | InProgress, Running = 0x40 | InProgress, RunQueued = 0x40 | InProgress | Waiting, InProgressMask = 0x10 | 0x20 | 0x40, // Finished results Succeeded = 0x100 | Finished, Failed = 0x200 | Finished, Ignored = 0x400 | Finished, DeviceNotFound = 0x800 | Finished, // Finished & Failed results Crashed = 0x1000 | Failed, TimedOut = 0x2000 | Failed, HarnessException = 0x4000 | Failed, LaunchFailure = 0x6000 | Failed, BuildFailure = 0x8000 | Failed, // Other results BuildSucceeded = 0x10000 | Succeeded, LaunchTimedOut = 0x20000 | LaunchFailure | TimedOut, } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/TestReporter.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.Serialization.Json; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.Common.Utilities; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Listeners; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; using ExceptionLogger = System.Action; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared; /// /// Class that gets the result of an executed test application, parses the results and provides information /// about the success or failure of the execution. /// public class TestReporter : ITestReporter { private const string TimeoutMessage = "Test run timed out after {0} minute(s)."; private const string CompletionMessage = "Test run completed"; private const string FailureMessage = "Test run failed"; private readonly ISimpleListener _listener; private readonly IFileBackedLog _mainLog; private readonly ILogs _crashLogs; private readonly IReadableLog _runLog; private readonly ILogs _logs; private readonly ICrashSnapshotReporter _crashReporter; private readonly IResultParser _resultParser; private readonly AppBundleInformation _appInfo; private readonly RunMode _runMode; private readonly XmlResultJargon _xmlJargon; private readonly IMlaunchProcessManager _processManager; private readonly string? _deviceName; private readonly TimeSpan _timeout; private readonly Stopwatch _timeoutWatch; /// /// Additional logs that will be sent with the report in case of a failure. /// Used by the Xamarin.Xharness project to add BuildTask logs. /// private readonly string? _additionalLogsDirectory; private readonly CancellationTokenSource _cancellationTokenSource = new(); /// /// Callback needed for the Xamarin.Xharness project that does extra logging in case of a crash. /// private readonly ExceptionLogger? _exceptionLogger; private bool _waitedForExit = true; private bool _launchFailure; private bool _isSimulatorTest; private bool _timedout; private readonly bool _generateHtml; public ILog CallbackLog { get; private set; } public bool? Success { get; private set; } public CancellationToken CancellationToken => _cancellationTokenSource.Token; public bool ResultsUseXml => _xmlJargon != XmlResultJargon.Missing; private bool TestExecutionStarted => _listener.ConnectedTask.IsCompletedSuccessfully && _listener.ConnectedTask.Result; public TestReporter( IMlaunchProcessManager processManager, IFileBackedLog mainLog, IReadableLog runLog, ILogs logs, ICrashSnapshotReporter crashReporter, ISimpleListener simpleListener, IResultParser parser, AppBundleInformation appInformation, RunMode runMode, XmlResultJargon xmlJargon, string? device, TimeSpan timeout, string? additionalLogsDirectory = null, ExceptionLogger? exceptionLogger = null, bool generateHtml = false) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); _deviceName = device; // can be null on simulators _listener = simpleListener ?? throw new ArgumentNullException(nameof(simpleListener)); _mainLog = mainLog ?? throw new ArgumentNullException(nameof(mainLog)); _runLog = runLog ?? throw new ArgumentNullException(nameof(runLog)); _logs = logs ?? throw new ArgumentNullException(nameof(logs)); _crashReporter = crashReporter ?? throw new ArgumentNullException(nameof(crashReporter)); _crashLogs = new Logs(logs.Directory); _resultParser = parser ?? throw new ArgumentNullException(nameof(parser)); _appInfo = appInformation ?? throw new ArgumentNullException(nameof(appInformation)); _runMode = runMode; _xmlJargon = xmlJargon; _timeout = timeout; _additionalLogsDirectory = additionalLogsDirectory; _exceptionLogger = exceptionLogger; _timeoutWatch = Stopwatch.StartNew(); _generateHtml = generateHtml; CallbackLog = new CallbackLog(line => { // MT1111: Application launched successfully, but it's not possible to wait for the app to exit as // requested because it's not possible to detect app termination when launching using gdbserver _waitedForExit &= line?.Contains("MT1111: ") != true; if (line?.Contains("error MT1007") == true) { _launchFailure = true; } }); } /// /// Parse the run log and decide if we managed to start the process or not /// private async Task GetPidFromRunLog() { int pid = -1; using var reader = _runLog.GetReader(); // diposed at the end of the method, which is what we want if (reader.Peek() == -1) { // Empty file! If the app never connected to our listener, it probably never launched if (!_listener.ConnectedTask.IsCompletedSuccessfully || !_listener.ConnectedTask.Result) { _launchFailure = true; } } else { string? line; while ((line = await reader.ReadLineAsync()) is not null) { if (line.StartsWith("Application launched. PID = ", StringComparison.Ordinal)) { var pidstr = line.Substring("Application launched. PID = ".Length); if (!int.TryParse(pidstr, out pid)) { _mainLog.WriteLine("Could not parse pid: {0}", pidstr); } } else if (line.Contains("Xamarin.Hosting: Launched ") && line.Contains(" with pid ")) { var pidstr = line.Substring(line.LastIndexOf(' ')); if (!int.TryParse(pidstr, out pid)) { _mainLog.WriteLine("Could not parse pid: {0}", pidstr); } } else if (line.Contains("error MT1008")) { _launchFailure = true; } } } return pid; } /// /// Parse the main log to get the pid /// private async Task GetPidFromMainLog() { int pid = -1; using var log_reader = _mainLog.GetReader(); // dispose when we leave the method, which is what we want string? line; while ((line = await log_reader.ReadLineAsync()) != null) { const string str = "was launched with pid '"; var idx = line.IndexOf(str, StringComparison.Ordinal); if (idx > 0) { idx += str.Length; var next_idx = line.IndexOf('\'', idx); if (next_idx > idx) { int.TryParse(line.Substring(idx, next_idx - idx), out pid); } } if (pid != -1) { return pid; } } return pid; } /// /// Return the reason for a crash found in a log /// private void GetCrashReason(int pid, IReadableLog crashLog, out string? crashReason) { crashReason = null; using var crashReader = crashLog.GetReader(); // dispose when we leave the method var text = crashReader.ReadToEnd(); var reader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(text), new XmlDictionaryReaderQuotas()); var doc = new XmlDocument(); doc.Load(reader); foreach (XmlNode? node in doc.SelectNodes($"/root/processes/item[pid = '" + pid + "']")) { Console.WriteLine(node?.InnerXml); Console.WriteLine(node?.SelectSingleNode("reason")?.InnerText); crashReason = node?.SelectSingleNode("reason")?.InnerText; } } /// /// Return if the tcp connection with the device failed /// private async Task TcpConnectionFailed() { using var reader = new StreamReader(_mainLog.FullPath); string? line; while ((line = await reader.ReadLineAsync()) != null) { if (line.Contains("Couldn't establish a TCP connection with any of the hostnames")) { return true; } } return false; } private Task KillAppProcess(int pid, CancellationTokenSource cancellationSource) { var launchTimedout = cancellationSource.IsCancellationRequested; var timeoutType = launchTimedout ? "Launch" : "Completion"; _mainLog.WriteLine($"{timeoutType} timed out after {_timeoutWatch.Elapsed.TotalSeconds} seconds"); return _processManager.KillTreeAsync(pid, _mainLog, true); } private async Task CollectResult(ProcessExecutionResult runResult) { if (!_waitedForExit && !runResult.TimedOut) { // mlaunch couldn't wait for exit for some reason. Let's assume the app exits when the test listener completes. _mainLog.WriteLine("Waiting for listener to complete, since mlaunch won't tell."); if (!await _listener.CompletionTask.TimeoutAfter(_timeout - _timeoutWatch.Elapsed)) { runResult.TimedOut = true; } } if (runResult.TimedOut) { _timedout = true; Success = false; _mainLog.WriteLine(TimeoutMessage, _timeout.TotalMinutes); } else if (runResult.Succeeded) { _mainLog.WriteLine(CompletionMessage); Success = true; } else { _mainLog.WriteLine(FailureMessage); Success = false; } } public void LaunchCallback(Task launchResult) { if (launchResult.IsFaulted) { _mainLog.WriteLine($"Test execution failed: {launchResult.Exception}"); return; } if (launchResult.IsCanceled) { _mainLog.WriteLine("Test execution was cancelled"); return; } if (launchResult.Result) { _mainLog.WriteLine("Test execution started"); return; } _cancellationTokenSource.Cancel(); _timedout = true; if (TestExecutionStarted) { _mainLog.WriteLine($"Test execution timed out after {_timeoutWatch.Elapsed.TotalMinutes:0.##} minutes"); return; } _mainLog.WriteLine($"Test failed to start in {_timeoutWatch.Elapsed.TotalMinutes:0.##} minutes"); } public async Task CollectSimulatorResult(ProcessExecutionResult runResult) { _isSimulatorTest = true; await CollectResult(runResult); if (Success != null && !Success.Value) { var pid = await GetPidFromRunLog(); if (pid > 0) { await KillAppProcess(pid, _cancellationTokenSource); } else { _mainLog.WriteLine("Could not find pid in mtouch output."); } } } public async Task CollectDeviceResult(ProcessExecutionResult runResult) { _isSimulatorTest = false; await CollectResult(runResult); } private async Task<(string? ResultLine, bool Failed)> GetResultLine(string logPath) { string? resultLine = null; bool failed = false; using var reader = new StreamReader(logPath); string? line = null; while ((line = await reader.ReadLineAsync()) != null) { if (line.Contains("Tests run:")) { Console.WriteLine(line); resultLine = line; break; } else if (line.Contains("[FAIL]")) { Console.WriteLine(line); failed = true; } } return (ResultLine: resultLine, Failed: failed); } private async Task<(string? resultLine, bool failed, bool crashed)> ParseResultFile(AppBundleInformation appInfo, string test_log_path, bool timed_out) { (string? resultLine, bool failed, bool crashed) parseResult = (null, false, false); if (!File.Exists(test_log_path)) { parseResult.crashed = true; // if we do not have a log file, the test crashes return parseResult; } // parsing the result is different if we are in jenkins or not. // When in Jenkins, Touch.Unit produces an xml file instead of a console log (so that we can get better test reporting). // However, for our own reporting, we still want the console-based log. This log is embedded inside the xml produced // by Touch.Unit, so we need to extract it and write it to disk. We also need to re-save the xml output, since Touch.Unit // wraps the NUnit xml output with additional information, which we need to unwrap so that Jenkins understands it. // // On the other hand, the nunit and xunit do not have that data and have to be parsed. // // This if statement has a small trick, we found out that internet sharing in some of the bots (VSTS) does not work, in // that case, we cannot do a TCP connection to xharness to get the log, this is a problem since if we did not get the xml // from the TCP connection, we are going to fail when trying to read it and not parse it. Therefore, we are not only // going to check if we are in CI, but also if the listener_log is valid. var path = Path.ChangeExtension(test_log_path, "xml"); if (path == test_log_path) { path = Path.Combine(Path.GetDirectoryName(path)!, Path.GetFileNameWithoutExtension(path) + "-clean.xml"); } _resultParser.CleanXml(test_log_path, path); if (ResultsUseXml && _resultParser.IsValidXml(path, out var xmlType)) { try { var newFilename = _resultParser.GetXmlFilePath(path, xmlType); // at this point, we have the test results, but we want to be able to have attachments in vsts, so if the format is // the right one (NUnitV3) add the nodes. ATM only TouchUnit uses V3. var testRunName = $"{appInfo.AppName} {appInfo.Variation}"; if (xmlType == XmlResultJargon.NUnitV3) { var logFiles = new List(); // add our logs AND the logs of the previous task, which is the build task logFiles.AddRange(Directory.GetFiles(_crashLogs.Directory)); if (_additionalLogsDirectory != null) // when using the run command, we do not have a build task, ergo, there are no logs to add. { logFiles.AddRange(Directory.GetFiles(_additionalLogsDirectory)); } // add the attachments and write in the new filename // add a final prefix to the file name to make sure that the VSTS test uploaded just pick // the final version, else we will upload tests more than once newFilename = XmlResultParser.GetVSTSFilename(newFilename); _resultParser.UpdateMissingData(path, newFilename, testRunName, logFiles); } else { // rename the path to the correct value File.Move(path, newFilename); } path = newFilename; if (_generateHtml) { // write the human readable results in a tmp file, which we later use to step on the logs var humanReadableLog = _logs.CreateFile(Path.GetFileNameWithoutExtension(test_log_path) + ".log", LogType.NUnitResult); (parseResult.resultLine, parseResult.failed) = _resultParser.ParseResults(path, xmlType, humanReadableLog); } else { (parseResult.resultLine, parseResult.failed) = _resultParser.ParseResults(path, xmlType, (StreamWriter?)null); } // we do not longer need the tmp file _logs.AddFile(path, LogType.XmlLog.ToString()); return parseResult; } catch (Exception e) { _mainLog.WriteLine("Could not parse xml result file: {0}", e); // print file for better debugging _mainLog.WriteLine("File data is:"); _mainLog.WriteLine(new string('#', 10)); using (var stream = new StreamReader(path)) { string? line; while ((line = await stream.ReadLineAsync()) != null) { _mainLog.WriteLine(line); } } _mainLog.WriteLine(new string('#', 10)); _mainLog.WriteLine("End of xml results."); if (timed_out) { WrenchLog.WriteLine($"AddSummary: {_runMode} timed out
"); return parseResult; } else { WrenchLog.WriteLine($"AddSummary: {_runMode} crashed
"); _mainLog.WriteLine("Test run crashed"); parseResult.crashed = true; return parseResult; } } } // delete not needed copy File.Delete(path); // not the most efficient way but this just happens when we run // the tests locally and we usually do not run all tests, we are // more interested to be efficent on the bots (parseResult.resultLine, parseResult.failed) = await GetResultLine(test_log_path); return parseResult; } private async Task<(bool Succeeded, bool Crashed, string ResultLine)> TestsSucceeded(AppBundleInformation appInfo, string test_log_path, bool timed_out) { var (resultLine, failed, crashed) = await ParseResultFile(appInfo, test_log_path, timed_out); // read the parsed logs in a human readable way if (resultLine != null) { var tests_run = resultLine.Replace("Tests run: ", ""); if (failed) { WrenchLog.WriteLine("AddSummary: {0} failed: {1}
", _runMode, tests_run); _mainLog.WriteLine("Test run failed"); return (false, crashed, resultLine); } else { WrenchLog.WriteLine("AddSummary: {0} succeeded: {1}
", _runMode, tests_run); _mainLog.WriteLine("Test run succeeded"); return (true, crashed, resultLine); } } else if (timed_out) { WrenchLog.WriteLine("AddSummary: {0} timed out
", _runMode); _mainLog.WriteLine("Test run timed out"); return (false, false, "Test run timed out"); } else { WrenchLog.WriteLine("AddSummary: {0} crashed
", _runMode); _mainLog.WriteLine("Test run crashed"); return (false, true, "Test run crashed"); } } /// /// Generate all the xml failures that will help the integration with the CI and return the failure reason /// private async Task GenerateXmlFailures(string failure, bool crashed, string? crashReason) { if (!ResultsUseXml) // nothing to do { return; } if (!string.IsNullOrEmpty(crashReason)) { _resultParser.GenerateFailure( _logs, "crash", _appInfo.AppName, _appInfo.Variation, $"App Crash {_appInfo.AppName} {_appInfo.Variation}", $"App crashed: {failure}", _mainLog.FullPath, _xmlJargon); return; } if (_launchFailure) { _resultParser.GenerateFailure( _logs, "launch", _appInfo.AppName, _appInfo.Variation, $"App Launch {_appInfo.AppName} {_appInfo.Variation} on {_deviceName}", $"{failure} on {_deviceName}", _mainLog.FullPath, _xmlJargon); return; } if (!_isSimulatorTest && crashed && string.IsNullOrEmpty(crashReason)) { // this happens more that what we would like on devices, the main reason most of the time is that we have had netwoking problems and the // tcp connection could not be stablished. We are going to report it as an error since we have not parsed the logs, evne when the app might have // not crashed. We need to check the main_log to see if we do have an tcp issue or not if (await TcpConnectionFailed()) { _resultParser.GenerateFailure( _logs, "tcp-connection", _appInfo.AppName, _appInfo.Variation, $"TcpConnection on {_deviceName}", $"Device {_deviceName} could not reach the host over tcp.", _mainLog.FullPath, _xmlJargon); } } else if (_timedout) { _resultParser.GenerateFailure( _logs, "timeout", _appInfo.AppName, _appInfo.Variation, $"App Timeout {_appInfo.AppName} {_appInfo.Variation} on bot {_deviceName}", $"{_appInfo.AppName} {_appInfo.Variation} Test run timed out after {_timeout.TotalMinutes} minute(s) on bot {_deviceName}.", _mainLog.FullPath, _xmlJargon); } } public async Task<(TestExecutingResult ExecutingResult, string? ResultMessage)> ParseResult() { (TestExecutingResult ExecutingResult, string? ResultMessage) result = (ExecutingResult: TestExecutingResult.Finished, ResultMessage: null); var crashed = false; if (File.Exists(_listener.TestLog.FullPath)) { WrenchLog.WriteLine("AddFile: {0}", _listener.TestLog.FullPath); (Success, crashed, result.ResultMessage) = await TestsSucceeded(_appInfo, _listener.TestLog.FullPath, _timedout); } else if (_timedout) { WrenchLog.WriteLine("AddSummary: {0} never launched
", _runMode); _mainLog.WriteLine("Test run never launched"); result.ResultMessage = "Test runner never started"; Success = false; } else if (_launchFailure) { WrenchLog.WriteLine("AddSummary: {0} failed to launch
", _runMode); _mainLog.WriteLine("Test run failed to launch"); result.ResultMessage = "Test runner failed to launch"; Success = false; } else if (Success == true) { // Test run completed (detected via app end signal or mlaunch exit) but the results file // was not found. This can happen when the device file copy (devicectl) fails due to // transient device communication issues (e.g., tvOS Mercury error 1000, RSD 0xE8000003). // Since we already confirmed test completion, treat this as success with a warning. WrenchLog.WriteLine("AddSummary: {0} completed but results unavailable
", _runMode); _mainLog.WriteLine("Test run completed but results file was not available (device communication issue)"); result.ResultMessage = "Test run completed but results file was not available"; // Success remains true } else { WrenchLog.WriteLine("AddSummary: {0} crashed at startup (no log)
", _runMode); _mainLog.WriteLine("Test run started but crashed and no test results were reported"); result.ResultMessage = "No test log file was produced"; crashed = true; Success = false; } if (!Success.HasValue) { Success = false; } var crashLogWaitTime = 0; if (!Success.Value) { crashLogWaitTime = 5; } if (crashed) { crashLogWaitTime = 30; } await _crashReporter.EndCaptureAsync(TimeSpan.FromSeconds(crashLogWaitTime)); if (_timedout) { if (TestExecutionStarted) { result.ExecutingResult = TestExecutingResult.TimedOut; } else { result.ExecutingResult = TestExecutingResult.LaunchTimedOut; } } else if (_launchFailure) { result.ExecutingResult = TestExecutingResult.LaunchFailure; } else if (crashed) { result.ExecutingResult = TestExecutingResult.Crashed; } else if (Success.Value) { result.ExecutingResult = TestExecutingResult.Succeeded; } else { result.ExecutingResult = TestExecutingResult.Failed; } // Check crash reports to see if any of them explains why the test run crashed. if (!Success.Value) { int pid = -1; string? crashReason = null; foreach (var crashLog in _crashLogs) { try { _logs.Add(crashLog); if (pid == -1) { // Find the pid pid = await GetPidFromMainLog(); } GetCrashReason(pid, crashLog, out crashReason); if (crashReason != null) { break; } } catch (Exception e) { var message = string.Format("Failed to process crash report '{1}': {0}", e.Message, crashLog.Description); _mainLog.WriteLine(message); _exceptionLogger?.Invoke(2, message); } } if (!string.IsNullOrEmpty(crashReason)) { if (crashReason == "per-process-limit") { result.ResultMessage = "Killed due to using too much memory (per-process-limit)."; } else { result.ResultMessage = $"Killed by the OS ({crashReason})"; } } else if (_launchFailure) { // same as with a crash result.ResultMessage = $"Launch failure"; } await GenerateXmlFailures(result.ResultMessage, crashed, crashReason); } return result; } public void Dispose() { _crashLogs.Dispose(); GC.SuppressFinalize(this); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/TestReporterFactory.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Listeners; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using ExceptionLogger = System.Action; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared; public interface ITestReporterFactory { ITestReporter Create(IFileBackedLog mainLog, IReadableLog runLog, ILogs logs, ICrashSnapshotReporter crashSnapshotReporter, ISimpleListener simpleListener, IResultParser parser, AppBundleInformation appInformation, RunMode runMode, XmlResultJargon xmlJargon, string? device, TimeSpan timeout, string? additionalLogsDirectory = null, ExceptionLogger? exceptionLogger = null, bool generateHtml = false); } public class TestReporterFactory : ITestReporterFactory { private readonly IMlaunchProcessManager _processManager; public TestReporterFactory(IMlaunchProcessManager processManager) { _processManager = processManager ?? throw new ArgumentNullException(nameof(processManager)); } public ITestReporter Create(IFileBackedLog mainLog, IReadableLog runLog, ILogs logs, ICrashSnapshotReporter crashReporter, ISimpleListener simpleListener, IResultParser parser, AppBundleInformation appInformation, RunMode runMode, XmlResultJargon xmlJargon, string? device, TimeSpan timeout, string? additionalLogsDirectory = null, ExceptionLogger? exceptionLogger = null, bool generateHtml = false) => new TestReporter(_processManager, mainLog, runLog, logs, crashReporter, simpleListener, parser, appInformation, runMode, xmlJargon, device, timeout, additionalLogsDirectory, exceptionLogger, generateHtml); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/TestTarget.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared; public enum TestTarget { None, Simulator_iOS64, Simulator_tvOS, Simulator_watchOS, Simulator_xrOS, Device_iOS, Device_tvOS, Device_watchOS, Device_xrOS, MacCatalyst, } public class TestTargetOs { public static readonly TestTargetOs None = new(TestTarget.None, null); /// /// Platform, i.e. Simulator iOS x64 /// public TestTarget Platform { get; } /// /// OS version, i.e. "13.4". /// public string? OSVersion { get; } public TestTargetOs(TestTarget platform, string? osVersion) { Platform = platform; OSVersion = osVersion; } } public static class TestTargetExtensions { public static RunMode ToRunMode(this TestTarget target) => target switch { TestTarget.Simulator_iOS64 => RunMode.iOS, TestTarget.Simulator_tvOS => RunMode.TvOS, TestTarget.Simulator_watchOS => RunMode.WatchOS, TestTarget.Simulator_xrOS => RunMode.xrOS, TestTarget.Device_iOS => RunMode.iOS, TestTarget.Device_tvOS => RunMode.TvOS, TestTarget.Device_watchOS => RunMode.WatchOS, TestTarget.Device_xrOS => RunMode.xrOS, TestTarget.MacCatalyst => RunMode.MacOS, _ => throw new ArgumentOutOfRangeException($"Unknown target: {target}"), }; public static bool IsSimulator(this TestTarget target) => target switch { TestTarget.Simulator_iOS64 => true, TestTarget.Simulator_tvOS => true, TestTarget.Simulator_watchOS => true, TestTarget.Simulator_xrOS => true, TestTarget.Device_iOS => false, TestTarget.Device_tvOS => false, TestTarget.Device_watchOS => false, TestTarget.Device_xrOS => false, TestTarget.MacCatalyst => true, _ => throw new ArgumentOutOfRangeException($"Unknown target: {target}"), }; public static bool IsWatchOSTarget(this TestTarget target) => target switch { TestTarget.Simulator_watchOS => true, TestTarget.Device_watchOS => true, _ => false, }; } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Utilities/DirectoryUtilities.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Runtime.InteropServices; namespace Microsoft.DotNet.XHarness.iOS.Shared.Utilities; // A class that creates temporary directories next to the test assembly, and cleans the output on startup // Advantages: // * The temporary directories are automatically cleaned on Wrench (unlike /tmp, which isn't) // * The temporary directories stay after a test is run (until a new test run is started), // which makes it easier to re-run (copy-paste) commands that failed. public static class DirectoryUtilities { private static readonly string s_root; private static int s_lastNumber; static DirectoryUtilities() { s_root = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "tmp-test-dir"); if (Directory.Exists(s_root)) { Directory.Delete(s_root, true); } Directory.CreateDirectory(s_root); } [DllImport("libc", SetLastError = true)] private static extern int mkdir(string path, ushort mode); public static string CreateTemporaryDirectory(string name = null) { if (string.IsNullOrEmpty(name)) { var calling_method = new System.Diagnostics.StackFrame(1).GetMethod(); if (calling_method != null) { name = calling_method.DeclaringType.FullName + "." + calling_method.Name; } else { name = "unknown-test"; } } var rv = Path.Combine(s_root, name); for (int i = s_lastNumber; i < 10000 + s_lastNumber; i++) { // There's no way to know if Directory.CreateDirectory // created the directory or not (which would happen if the directory // already existed). Checking if the directory exists before // creating it would result in a race condition if multiple // threads create temporary directories at the same time. if (mkdir(rv, Convert.ToUInt16("777", 8)) == 0) { s_lastNumber = i; return rv; } rv = Path.Combine(s_root, name + i); } throw new Exception("Could not create temporary directory"); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Utilities/Extensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Utilities; public static class Extensions { public static string AsString(this TestTarget target) => target switch { TestTarget.Device_iOS => "ios-device", TestTarget.Device_tvOS => "tvos-device", TestTarget.Device_watchOS => "watchos-device", TestTarget.Device_xrOS => "xros-device", TestTarget.Simulator_iOS64 => "ios-simulator-64", TestTarget.Simulator_tvOS => "tvos-simulator", TestTarget.Simulator_watchOS => "watchos-simulator", TestTarget.Simulator_xrOS => "xros-simulator", TestTarget.MacCatalyst => "maccatalyst", _ => throw new ArgumentOutOfRangeException(nameof(target)) }; public static string AsString(this TestTargetOs targetOs) => targetOs.Platform.AsString() + (targetOs.OSVersion != null ? "_" + targetOs.OSVersion : null); public static TestTarget ParseAsAppRunnerTarget(this string target) => target switch { "ios-device" => TestTarget.Device_iOS, "tvos-device" => TestTarget.Device_tvOS, "watchos-device" => TestTarget.Device_watchOS, "xros-device" => TestTarget.Device_xrOS, "ios-simulator" => TestTarget.Simulator_iOS64, "ios-simulator-64" => TestTarget.Simulator_iOS64, "tvos-simulator" => TestTarget.Simulator_tvOS, "watchos-simulator" => TestTarget.Simulator_watchOS, "xros-simulator" => TestTarget.Simulator_xrOS, "maccatalyst" => TestTarget.MacCatalyst, null => TestTarget.None, "" => TestTarget.None, _ => throw new ArgumentOutOfRangeException(nameof(target)) }; public static TestTargetOs ParseAsAppRunnerTargetOs(this string targetName) { var index = targetName.LastIndexOf('_'); TestTarget target; string? osVersion = null; if (index != -1) { target = targetName.Substring(0, index).ParseAsAppRunnerTarget(); osVersion = targetName.Substring(index + 1); } else { target = targetName.ParseAsAppRunnerTarget(); } return new TestTargetOs(target, osVersion); } public static Extension ParseFromNSExtensionPointIdentifier(this string identifier) => identifier switch { "com.apple.widget-extension" => Extension.TodayExtension, "com.apple.watchkit" => Extension.WatchKit2, _ => throw new ArgumentOutOfRangeException(nameof(identifier)) }; public static string AsNSExtensionPointIdentifier(this Extension extension) => extension switch { Extension.TodayExtension => "com.apple.widget-extension", Extension.WatchKit2 => "com.apple.watchkit", _ => throw new ArgumentOutOfRangeException(nameof(extension)) }; public static void DoNotAwait(this Task task) { // Don't do anything! // // Here's why: // If you want to run a task in the background, and you don't care about the result, this is the obvious way to do so: // // DoSomethingAsync (); // // which works fine, but the compiler warns that: // // Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call. // // One potential fix is to assign the return value to variable: // // var x = DoSomethingAsync (); // // But this creates unnecessary variables. It's also still slightly confusing (why assign to a variable that's not used?). // This extension method allows us to be more explicit: // // DoSomethingAsync ().DoNotAwait (); // // This makes it abundantly clear that the intention is to not await 'DoSomething', and no warnings will be shown either. } public static IEnumerable Shuffle(this IEnumerable collection) { var rnd = new Random((int)DateTime.Now.Ticks); return collection.OrderBy(v => rnd.Next()); } public static string AsHtml(this string inString) { var rv = System.Web.HttpUtility.HtmlEncode(inString); return rv.Replace("\t", "    ").Replace("\n", "
\n"); } // XmlWriter.WriteCData will throw for some characters. This method will catch that exception, and write a base64 encoded string instead (which should always be safe). public static void WriteCDataSafe(this XmlWriter writer, string text) { try { writer.WriteCData(text); } catch (ArgumentException) { var utf8 = Encoding.UTF8.GetBytes(text); var base64 = Convert.ToBase64String(utf8); writer.WriteCData("Base64 encoded UTF8 string: " + base64); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Utilities/Helpers.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; namespace Microsoft.DotNet.XHarness.iOS.Shared.Utilities; public interface IHelpers { string GetTerminalName(int filedescriptor); Guid GenerateStableGuid(string seed = null); Guid GenerateGuid(); string Timestamp { get; } IEnumerable GetLocalIpAddresses(); } public class Helpers : IHelpers { // We want guids that nobody else has, but we also want to generate the same guid // on subsequent invocations (so that csprojs don't change unnecessarily, which is // annoying when XS reloads the projects, and also causes unnecessary rebuilds). // Nothing really breaks when the sequence isn't identical from run to run, so // this is just a best minimal effort. private static readonly Random s_guidGenerator = new(unchecked((int)0xdeadf00d)); public Guid GenerateStableGuid(string seed = null) { var bytes = new byte[16]; if (seed == null) { s_guidGenerator.NextBytes(bytes); } else { using (var provider = SHA256.Create()) { var inputBytes = Encoding.UTF8.GetBytes(seed); var output = provider.ComputeHash(inputBytes); Array.Copy(output, bytes, 16); } } return new Guid(bytes); } public Guid GenerateGuid() => Guid.NewGuid(); public string Timestamp => $"{DateTime.Now:yyyyMMdd_HHmmss}"; [DllImport("/usr/lib/libc.dylib")] private static extern IntPtr ttyname(int filedes); public string GetTerminalName(int filedescriptor) => Marshal.PtrToStringAuto(ttyname(filedescriptor)); public IEnumerable GetLocalIpAddresses() { var ipv4Addresses = new List(); var otherAddresses = new List(); foreach (var networkInterface in NetworkInterface.GetAllNetworkInterfaces().Where(adapter => adapter.OperationalStatus == OperationalStatus.Up)) { var interfaceInfos = networkInterface.GetIPProperties().UnicastAddresses.Where(info => !IPAddress.IsLoopback(info.Address)); foreach (UnicastIPAddressInformation info in interfaceInfos) { if (info.Address.AddressFamily == AddressFamily.InterNetwork) { ipv4Addresses.Add(info.Address); } else { otherAddresses.Add(info.Address); } } } return ipv4Addresses.Concat(otherAddresses); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Utilities/PlistExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Runtime.CompilerServices; using System.Xml; namespace Microsoft.DotNet.XHarness.iOS.Shared.Utilities; public static class PListExtensions { private static readonly ConditionalWeakTable s_filenames = new(); public const string BundleExecutablePropertyName = "CFBundleExecutable"; public const string BundleIdentifierPropertyName = "CFBundleIdentifier"; public const string BundleNamePropertyName = "CFBundleName"; public const string RequiredDeviceCapabilities = "UIRequiredDeviceCapabilities"; public static string GetFilename(this XmlDocument doc) { s_filenames.TryGetValue(doc, out var rv); return rv; } public static void LoadWithoutNetworkAccess(this XmlDocument doc, string filename) { using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) { var settings = new XmlReaderSettings() { XmlResolver = null, DtdProcessing = DtdProcessing.Parse, }; using (var reader = XmlReader.Create(fs, settings)) { doc.Load(reader); } } s_filenames.Add(doc, filename); } public static void LoadXmlWithoutNetworkAccess(this XmlDocument doc, string xml) { using (var fs = new StringReader(xml)) { var settings = new XmlReaderSettings() { XmlResolver = null, DtdProcessing = DtdProcessing.Parse, }; using (var reader = XmlReader.Create(fs, settings)) { doc.Load(reader); } } } public static void SetMinimumOSVersion(this XmlDocument plist, string value) => plist.SetPListStringValue("MinimumOSVersion", value); public static void SetMinimummacOSVersion(this XmlDocument plist, string value) => plist.SetPListStringValue("LSMinimumSystemVersion", value); public static void SetCFBundleDisplayName(this XmlDocument plist, string value) => plist.SetPListStringValue("CFBundleDisplayName", value); public static string GetCFBundleDisplayName(this XmlDocument plist) => plist.GetPListStringValue("CFBundleDisplayName"); public static string GetCFBundleExecutable(this XmlDocument plist) { TryGetPListStringValue(plist, BundleExecutablePropertyName, out var value); return value; } public static string GetMinimumOSVersion(this XmlDocument plist) => plist.GetPListStringValue("MinimumOSVersion"); public static string GetMinimummacOSVersion(this XmlDocument plist) => plist.GetPListStringValue("LSMinimumSystemVersion"); public static void SetCFBundleIdentifier(this XmlDocument plist, string value) => plist.SetPListStringValue(BundleIdentifierPropertyName, value); public static void SetCFBundleName(this XmlDocument plist, string value) => plist.SetPListStringValue(BundleNamePropertyName, value); public static void SetUIDeviceFamily(this XmlDocument plist, params int[] families) => plist.SetPListArrayOfIntegerValues("UIDeviceFamily", families); public static string GetCFBundleIdentifier(this XmlDocument plist) => plist.GetPListStringValue(BundleIdentifierPropertyName); public static string GetCFBundleName(this XmlDocument plist) => plist.GetPListStringValue(BundleNamePropertyName); public static string GetNSExtensionPointIdentifier(this XmlDocument plist) { TryGetPListStringValue(plist, "NSExtensionPointIdentifier", out var value); return value; } public static void SetPListStringValue(this XmlDocument plist, string node, string value) { if (node == null) { throw new ArgumentNullException(nameof(node)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } var element = plist.SelectSingleNode("//dict/key[text()='" + node + "']"); if (element == null) { plist.AddPListStringValue(node, value); } else { element.NextSibling.InnerText = value; } } public static void AddPListStringValue(this XmlDocument plist, string node, string value) { var keyElement = plist.CreateElement("key"); keyElement.InnerText = node; var valueElement = plist.CreateElement("string"); valueElement.InnerText = value; var root = plist.SelectSingleNode("//dict"); root.AppendChild(keyElement); root.AppendChild(valueElement); } public static void AddPListKeyValuePair(this XmlDocument plist, string node, string valueType, string value) { var keyElement = plist.CreateElement("key"); keyElement.InnerText = node; var valueElement = plist.CreateElement(valueType); valueElement.InnerXml = value; var root = plist.SelectSingleNode("//dict"); root.AppendChild(keyElement); root.AppendChild(valueElement); } public static bool ContainsKey(this XmlDocument plist, string key) => TryGetPListStringValue(plist, key, out _); private static void SetPListArrayOfIntegerValues(this XmlDocument plist, string node, params int[] values) { var key = plist.SelectSingleNode("//dict/key[text()='" + node + "']"); key.ParentNode.RemoveChild(key.NextSibling); var array = plist.CreateElement("array"); foreach (var value in values) { var element = plist.CreateElement("integer"); element.InnerText = value.ToString(); array.AppendChild(element); } key.ParentNode.InsertAfter(array, key); } private static string GetPListStringValue(this XmlDocument plist, string node) => plist.SelectSingleNode("//dict/key[text()='" + node + "']").NextSibling.InnerText; private static bool TryGetPListStringValue(this XmlDocument plist, string nodeName, out string value) { var node = plist.SelectSingleNode("//dict/key[text()='" + nodeName + "']")?.NextSibling; if (node == null) { value = null; return false; } value = node.InnerText; return true; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/Utilities/ProjectFileExtensions.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Xml; using Microsoft.DotNet.XHarness.Common.Utilities; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.Utilities; public static class ProjectFileExtensions { private const string MSBuild_Namespace = "http://schemas.microsoft.com/developer/msbuild/2003"; private static readonly string[] s_eqsplitter = new string[] { "==" }; private static readonly string[] s_orsplitter = new string[] { " Or " }; private static readonly char[] s_pipesplitter = new char[] { '|' }; private static readonly char[] s_trimchars = new char[] { '\'', ' ' }; public static void SetProjectTypeGuids(this XmlDocument csproj, string value) => SetNode(csproj, "ProjectTypeGuids", value); public static string GetProjectGuid(this XmlDocument csproj) => csproj.SelectSingleNode("/*/*/*[local-name() = 'ProjectGuid']").InnerText; public static void SetProjectGuid(this XmlDocument csproj, string value) => csproj.SelectSingleNode("/*/*/*[local-name() = 'ProjectGuid']").InnerText = value; public static string GetOutputType(this XmlDocument csproj) => csproj.SelectSingleNode("/*/*/*[local-name() = 'OutputType']").InnerText; public static void SetOutputType(this XmlDocument csproj, string value) => csproj.SelectSingleNode("/*/*/*[local-name() = 'OutputType']").InnerText = value; private static void ParseConditions(this XmlNode node, out string platform, out string configuration) { // This parses the platform/configuration out of conditions like this: // // Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' " // platform = "Any CPU"; configuration = "Debug"; while (node != null) { if (node.Attributes != null) { var conditionAttribute = node.Attributes["Condition"]; if (conditionAttribute != null) { var condition = conditionAttribute.Value; var eqsplit = condition.Split(s_eqsplitter, StringSplitOptions.None); if (eqsplit.Length == 2) { var left = eqsplit[0].Trim(s_trimchars).Split(s_pipesplitter); var right = eqsplit[1].Trim(s_trimchars).Split(s_pipesplitter); if (left.Length == right.Length) { for (int i = 0; i < left.Length; i++) { switch (left[i]) { case "$(Configuration)": configuration = right[i]; break; case "$(Platform)": platform = right[i]; break; default: throw new Exception(string.Format("Unknown condition logic: {0}", left[i])); } } } } if (string.IsNullOrEmpty(platform) || string.IsNullOrEmpty(configuration)) { throw new Exception(string.Format("Could not parse the condition: {0}", conditionAttribute.Value)); } } } node = node.ParentNode; } if (string.IsNullOrEmpty(platform) || string.IsNullOrEmpty(configuration)) { throw new Exception("Could not find a condition attribute."); } } public static void SetOutputPath(this XmlDocument csproj, string value, bool expand = true) { var nodes = csproj.SelectNodes("/*/*/*[local-name() = 'OutputPath']"); if (nodes == null || nodes.Count == 0) { throw new Exception("Could not find node OutputPath"); } foreach (XmlNode? n in nodes) { if (n == null) { continue; } if (expand) { // OutputPath needs to be expanded, otherwise Xamarin Studio isn't able to launch the project. ParseConditions(n, out string platform, out string configuration); n.InnerText = value.Replace("$(Platform)", platform).Replace("$(Configuration)", configuration); } else { n.InnerText = value; } } } private static bool IsNodeApplicable(XmlNode node, string? platform, string? configuration) { while (node != null) { if (!EvaluateCondition(node, platform, configuration)) { return false; } node = node.ParentNode; } return true; } private static bool EvaluateCondition([NotNullWhen(true)] XmlNode? node, string? platform, string? configuration) { if (node?.Attributes == null) { return true; } var condition = node.Attributes["Condition"]; if (condition == null) { return true; } var conditionValue = condition.Value; if (configuration != null) { conditionValue = conditionValue.Replace("$(Configuration)", configuration); } if (platform != null) { conditionValue = conditionValue.Replace("$(Platform)", platform); } var orsplits = conditionValue.Split(s_orsplitter, StringSplitOptions.None); foreach (var orsplit in orsplits) { var eqsplit = orsplit.Split(s_eqsplitter, StringSplitOptions.None); if (eqsplit.Length != 2) { Console.WriteLine("Could not parse condition; {0}", conditionValue); return false; } var left = eqsplit[0].Trim(s_trimchars); var right = eqsplit[1].Trim(s_trimchars); if (left == right) { return true; } } return false; } public static string? GetOutputPath(this XmlDocument csproj, string? platform, string? configuration) => GetElementValue(csproj, platform, configuration, "OutputPath", throwIfNotFound: false); public static string? GetMtouchArch(this XmlDocument csproj, string? platform, string? configuration) => GetElementValue(csproj, platform, configuration, "MtouchArch", throwIfNotFound: false); private static string? GetElementValue(this XmlDocument csproj, string? platform, string? configuration, string elementName, bool throwIfNotFound = true) { var nodes = csproj.SelectNodes($"/*/*/*[local-name() = '{elementName}']"); foreach (XmlNode? n in nodes) { if (n == null) { continue; } if (IsNodeApplicable(n, platform, configuration)) { return n.InnerText.Replace("$(Platform)", platform).Replace("$(Configuration)", configuration); } } if (throwIfNotFound) { throw new Exception($"Could not find {elementName}"); } return null; } public static string GetOutputAssemblyPath(this XmlDocument csproj, string? platform, string? configuration) { var outputPath = GetOutputPath(csproj, platform, configuration); var assemblyName = GetElementValue(csproj, platform, configuration, "AssemblyName"); string outputType = GetElementValue(csproj, platform, configuration, "OutputType") ?? throw new Exception("Failed to find the OutputType property"); string extension = (outputType.ToLowerInvariant()) switch { "library" => "dll", "exe" => "exe", _ => throw new NotImplementedException(outputType), }; return outputPath + "\\" + assemblyName + "." + extension; // MSBuild-style paths. } public static string? GetIsBindingProject(this XmlDocument csproj) => GetElementValue(csproj, string.Empty, string.Empty, "IsBindingProject", throwIfNotFound: false); public static void SetIntermediateOutputPath(this XmlDocument csproj, string value) { // Set any existing IntermediateOutputPath var nodes = csproj.SelectNodes("/*/*/*[local-name() = 'IntermediateOutputPath']"); var hasToplevel = false; if (nodes.Count != 0) { foreach (XmlNode? n in nodes) { if (n == null) { continue; } n.InnerText = value; hasToplevel |= n.Attributes["Condition"] == null; } } if (hasToplevel) { return; } // Make sure there's a top-level version too. var property_group = csproj.SelectSingleNode("/*/*[local-name() = 'PropertyGroup' and not(@Condition)]"); var intermediateOutputPath = csproj.CreateElement("IntermediateOutputPath", csproj.GetNamespace()); intermediateOutputPath.InnerText = value; property_group.AppendChild(intermediateOutputPath); } public static void SetTargetFrameworkIdentifier(this XmlDocument csproj, string value) => SetTopLevelPropertyGroupValue(csproj, "TargetFrameworkIdentifier", value); public static void SetTopLevelPropertyGroupValue(this XmlDocument csproj, string key, string value) { var firstPropertyGroups = csproj.SelectNodes("//*[local-name() = 'PropertyGroup']")[0]; var targetFrameworkIdentifierNode = firstPropertyGroups.SelectSingleNode(string.Format("//*[local-name() = '{0}']", key)); if (targetFrameworkIdentifierNode != null) { SetNode(csproj, key, value); } else { var mea = csproj.CreateElement(key, csproj.GetNamespace()); mea.InnerText = value; firstPropertyGroups.AppendChild(mea); } } public static void RemoveTargetFrameworkIdentifier(this XmlDocument csproj) => RemoveNode(csproj, "TargetFrameworkIdentifier", throwOnInexistentNode: false); public static void SetAssemblyName(this XmlDocument csproj, string value) => SetNode(csproj, "AssemblyName", value); public static string GetAssemblyName(this XmlDocument csproj) { var assemblyNameNode = csproj.SelectSingleNode("/*/*/*[local-name() = 'AssemblyName']"); if (assemblyNameNode != null) { return assemblyNameNode.InnerText; } return Path.GetFileNameWithoutExtension(csproj.GetFilename()); } public static void SetPlatformAssembly(this XmlDocument csproj, string value) => SetAssemblyReference(csproj, "Xamarin.iOS", value); public static void SetAssemblyReference(this XmlDocument csproj, string current, string value) { var reference = csproj.SelectSingleNode("/*/*/*[local-name() = 'Reference' and @Include = '" + current + "']"); if (reference != null) { reference.Attributes["Include"].Value = value; } } public static void RemoveReferences(this XmlDocument csproj, string projectName) { var reference = csproj.SelectSingleNode("/*/*/*[local-name() = 'Reference' and @Include = '" + projectName + "']"); if (reference != null) { reference.ParentNode.RemoveChild(reference); } } public static void RemovePackageReference(this XmlDocument csproj, string projectName) { var reference = csproj.SelectSingleNode("/*/*/*[local-name() = 'PackageReference' and @Include = '" + projectName + "']"); if (reference != null) { reference.ParentNode.RemoveChild(reference); } } public static void AddCompileInclude(this XmlDocument csproj, string link, string include, bool prepend = false) => AddInclude(csproj, "Compile", link, include, prepend); public static void AddInclude(this XmlDocument csproj, string type, string link, string include, bool prepend = false) { var type_node = csproj.SelectSingleNode($"//*[local-name() = '{type}']"); var item_group = type_node?.ParentNode ?? csproj.SelectSingleNode($"//*[local-name() = 'ItemGroup'][last()]"); var node = csproj.CreateElement(type, csproj.GetNamespace()); var include_attribute = csproj.CreateAttribute("Include"); include_attribute.Value = include; node.Attributes.Append(include_attribute); var linkElement = csproj.CreateElement("Link", csproj.GetNamespace()); linkElement.InnerText = link; node.AppendChild(linkElement); if (prepend) { item_group.PrependChild(node); } else { item_group.AppendChild(node); } } public static void FixCompileInclude(this XmlDocument csproj, string include, string newInclude) => csproj.SelectSingleNode($"//*[local-name() = 'Compile' and @Include = '{include}']").Attributes["Include"].Value = newInclude; public static void AddInterfaceDefinition(this XmlDocument csproj, string include) { var itemGroup = csproj.CreateItemGroup(); var id = csproj.CreateElement("InterfaceDefinition", csproj.GetNamespace()); var attrib = csproj.CreateAttribute("Include"); attrib.Value = include; id.Attributes.Append(attrib); itemGroup.AppendChild(id); } public static void SetImport(this XmlDocument csproj, string value) { var import = GetImport(csproj); if (string.IsNullOrEmpty(import)) { throw new Exception($"Could not find the xamarin import"); } var imports = csproj.SelectNodes($"/*/*[local-name() = 'Import'][@Project = '{import}']"); if (imports.Count != 1) { throw new Exception($"Found {imports.Count} xamarin imports?"); } imports[0].Attributes["Project"].Value = value; } public static void SetExtraLinkerDefs(this XmlDocument csproj, string value) { var mtouchExtraArgs = csproj.SelectNodes("//*[local-name() = 'MtouchExtraArgs']"); foreach (XmlNode? mea in mtouchExtraArgs) { if (mea == null) { continue; } mea.InnerText = mea.InnerText.Replace("extra-linker-defs.xml", value); } var nones = csproj.SelectNodes("//*[local-name() = 'None' and @Include = 'extra-linker-defs.xml']"); foreach (XmlNode? none in nones) { if (none == null) { continue; } none.Attributes["Include"].Value = value; } } public static void AddExtraMtouchArgs(this XmlDocument csproj, string value, string? platform, string? configuration) => AddToNode(csproj, "MtouchExtraArgs", value, platform, configuration); public static void AddMonoBundlingExtraArgs(this XmlDocument csproj, string value, string? platform, string? configuration) => AddToNode(csproj, "MonoBundlingExtraArgs", value, platform, configuration); public static void AddToNode(this XmlDocument csproj, string node, string value, string? platform, string? configuration) { var nodes = csproj.SelectNodes($"//*[local-name() = '{node}']"); foreach (XmlNode? mea in nodes) { if (mea == null || !IsNodeApplicable(mea, platform, configuration)) { continue; } if (mea.InnerText.Length > 0 && mea.InnerText[mea.InnerText.Length - 1] != ' ') { mea.InnerText += " "; } mea.InnerText += value; return; } // The project might not have this node, so create one of none was found. var propertyGroups = csproj.SelectNodes("//*[local-name() = 'PropertyGroup' and @Condition]").Cast(); var propertyGroup = propertyGroups.FirstOrDefault(v => EvaluateCondition(v, platform, configuration)); if (propertyGroup == null) { propertyGroup = csproj.AddPropertyGroup(platform, configuration); } var newNode = csproj.CreateElement(node, csproj.GetNamespace()); newNode.InnerText = value; propertyGroup.AppendChild(newNode); } public static string? GetMtouchLink(this XmlDocument csproj, string? platform, string? configuration) => GetNode(csproj, "MtouchLink", platform, configuration); public static void SetMtouchUseLlvm(this XmlDocument csproj, bool value, string? platform, string? configuration) => SetNode(csproj, "MtouchUseLlvm", true ? "true" : "false", platform, configuration); public static void SetMtouchUseBitcode(this XmlDocument csproj, bool value, string? platform, string? configuration) => SetNode(csproj, "MtouchEnableBitcode", true ? "true" : "false", platform, configuration); public static IEnumerable GetPropertyGroups(this XmlDocument csproj, string? platform, string? configuration) { var propertyGroups = csproj.SelectNodes("//*[local-name() = 'PropertyGroup' and @Condition]"); foreach (XmlNode? node in propertyGroups) { if (node == null) { continue; } if (!EvaluateCondition(node, platform, configuration)) { continue; } yield return node; } } public static void SetNode(this XmlDocument csproj, string node, string value, string? platform, string? configuration) { var projnode = csproj.SelectElementNodes(node); var found = false; foreach (XmlNode xmlnode in projnode) { if (!IsNodeApplicable(xmlnode, platform, configuration)) { continue; } xmlnode.InnerText = value; found = true; } if (found) { return; } // Not all projects have a MtouchExtraArgs node, so create one of none was found. var propertyGroups = csproj.SelectNodes("//*[local-name() = 'PropertyGroup' and @Condition]"); foreach (XmlNode? pg in propertyGroups) { if (!EvaluateCondition(pg, platform, configuration)) { continue; } var mea = csproj.CreateElement(node, csproj.GetNamespace()); mea.InnerText = value; pg.AppendChild(mea); } } private static string? GetNode(this XmlDocument csproj, string name, string? platform, string? configuration) { foreach (var pg in GetPropertyGroups(csproj, platform, configuration)) { foreach (XmlNode? node in pg.ChildNodes) { if (node?.Name == name) { return node.InnerText; } } } return null; } public static List GetImports(this XmlDocument csproj) { var imports = csproj.SelectNodes("/*/*[local-name() = 'Import'][not(@Condition)]"); var rv = new List(); foreach (XmlNode? import in imports) { if (import == null) { continue; } rv.Add(import.Attributes["Project"].Value); } return rv; } public static string GetImport(this XmlDocument csproj) => GetImports(csproj).FirstOrDefault((v) => v.Replace('/', '\\').Contains("$(MSBuildExtensionsPath)\\Xamarin")); public delegate bool FixReferenceDelegate(string include, string? subdir, string suffix, out string fixed_include); public static void FixProjectReferences(this XmlDocument csproj, string suffix, FixReferenceDelegate fixCallback) => FixProjectReferences(csproj, null, suffix, fixCallback); public static void FixProjectReferences(this XmlDocument csproj, string? subdir, string suffix, FixReferenceDelegate fixCallback) { var nodes = csproj.SelectNodes("/*/*/*[local-name() = 'ProjectReference']"); foreach (XmlNode? n in nodes) { if (n == null) { continue; } var nameNode = n["Name"]; var includeAttribute = n.Attributes["Include"]; var include = includeAttribute.Value; include = include.Replace('\\', '/'); if (!fixCallback(include, subdir, suffix, out var fixed_include)) { continue; } var name = Path.GetFileNameWithoutExtension(fixed_include); fixed_include = fixed_include.Replace('/', '\\'); includeAttribute.Value = fixed_include; if (nameNode != null) { nameNode.InnerText = name; } } } public static void FixTestLibrariesReferences(this XmlDocument csproj, string platform) { var nodes = csproj.SelectNodes("//*[local-name() = 'ObjcBindingNativeLibrary' or local-name() = 'ObjcBindingNativeFramework' or local-name() = 'NativeReference']"); var test_libraries = new string[] { "libtest.a", "libtest2.a", "XTest.framework", "XStaticArTest.framework", "XStaticObjectTest.framework" }; foreach (XmlNode? node in nodes) { if (node == null) { continue; } var includeAttribute = node.Attributes["Include"]; if (includeAttribute != null) { foreach (var tl in test_libraries) { includeAttribute.Value = includeAttribute.Value.Replace($"test-libraries\\.libs\\ios-fat\\{tl}", $"test-libraries\\.libs\\{platform}-fat\\{tl}"); } } } nodes = csproj.SelectNodes("//*[local-name() = 'Target' and @Name = 'BeforeBuild']"); foreach (XmlNode? node in nodes) { if (node == null) { continue; } var outputsAttribute = node.Attributes["Outputs"]; if (outputsAttribute != null) { foreach (var tl in test_libraries) { outputsAttribute.Value = outputsAttribute.Value.Replace($"test-libraries\\.libs\\ios-fat\\${tl}", $"test-libraries\\.libs\\{platform}-fat\\${tl}"); } } } } public static void FixArchitectures(this XmlDocument csproj, string simulator_arch, string device_arch, string? platform = null, string? configuration = null) { var nodes = csproj.SelectNodes("/*/*/*[local-name() = 'MtouchArch']"); if (nodes.Count == 0) { throw new Exception(string.Format("Could not find MtouchArch at all")); } foreach (XmlNode? n in nodes) { if (n == null || platform != null && configuration != null && !IsNodeApplicable(n, platform, configuration)) { continue; } switch (n.InnerText.ToLower()) { case "i386": case "x86_64": case "i386, x86_64": n.InnerText = simulator_arch; break; case "armv7": case "armv7s": case "arm64": case "arm64_32": case "armv7k": case "armv7, arm64": case "armv7k, arm64_32": n.InnerText = device_arch; break; default: throw new NotImplementedException(string.Format("Unhandled architecture: {0}", n.InnerText)); } } } public static void FindAndReplace(this XmlDocument csproj, string find, string replace) => FindAndReplace(csproj.ChildNodes, find, replace); private static void FindAndReplace(XmlNode node, string find, string replace) { if (node.HasChildNodes) { FindAndReplace(node.ChildNodes, find, replace); } else { if (node.NodeType == XmlNodeType.Text) { node.InnerText = node.InnerText.Replace(find, replace); } } if (node.Attributes != null) { foreach (XmlAttribute? attrib in node.Attributes) { if (attrib == null) { continue; } attrib.Value = attrib.Value.Replace(find, replace); } } } private static void FindAndReplace(XmlNodeList nodes, string find, string replace) { foreach (XmlNode? node in nodes) { if (node == null) { continue; } FindAndReplace(node, find, replace); } } public static void FixInfoPListInclude(this XmlDocument csproj, string suffix, string? fullPath = null, string? newName = null) { var import = GetInfoPListNode(csproj); if (import != null) { var attrib = import.Attributes["Include"]; var value = attrib.Value; var unixValue = value.Replace('\\', '/'); // If newName is specified, use that as-is // If not: // If the existing value has a directory, use that as the directory // Otherwise, if fullPath is passed, use that as the directory // Finally, combine the expected Info.plist name with the directory (if there is a directory; there might not be one) if (newName == null) { var directory = Path.GetDirectoryName(unixValue); if (string.IsNullOrEmpty(directory)) { directory = fullPath; } newName = $"Info{suffix}.plist"; if (!string.IsNullOrEmpty(directory)) { newName = Path.Combine(directory, newName); } } attrib.Value = newName.Replace('/', '\\'); var logicalName = import.SelectSingleNode("./*[local-name() = 'LogicalName']"); if (logicalName == null) { logicalName = csproj.CreateElement("LogicalName", csproj.GetNamespace()); import.AppendChild(logicalName); } logicalName.InnerText = "Info.plist"; } } public static string? GetNamespace(this XmlDocument csproj) => IsDotNetProject(csproj) ? null : MSBuild_Namespace; public static bool IsDotNetProject(this XmlDocument csproj) { var project = csproj?.SelectSingleNode("./*[local-name() = 'Project']"); var attrib = project?.Attributes["Sdk"]; return attrib != null; } public static bool? GetEnableDefaultItems(this XmlDocument csproj) { var node = csproj.SelectSingleNode($"/*/*/*[local-name() = 'EnableDefaultItems']"); if (node == null) { return null; } return string.Equals(node.InnerText, "true", StringComparison.OrdinalIgnoreCase); } public static XmlNode? GetInfoPListNode(this XmlDocument csproj) { var noLogicalName = csproj.SelectSingleNode("//*[(local-name() = 'None' or local-name() = 'BundleResource' or local-name() = 'Content') and @Include = 'Info.plist']"); if (noLogicalName != null) { return noLogicalName; } var logicalName = csproj.SelectSingleNode("//*[(local-name() = 'None' or local-name() = 'Content' or local-name() = 'BundleResource')]/*[local-name()='LogicalName' and text() = 'Info.plist']"); if (logicalName != null) { return logicalName.ParentNode; } var linkName = csproj.SelectSingleNode("//*[(local-name() = 'None' or local-name() = 'Content' or local-name() = 'BundleResource')]/*[local-name()='Link' and text() = 'Info.plist']"); if (linkName != null) { return linkName.ParentNode; } return null; } public static string? GetInfoPListInclude(this XmlDocument csproj) => GetInfoPListNode(csproj)?.Attributes["Include"]?.Value; public static IEnumerable GetProjectReferences(this XmlDocument csproj) { var nodes = csproj.SelectNodes("//*[local-name() = 'ProjectReference']"); foreach (XmlNode? node in nodes) { if (node == null) { continue; } yield return node.Attributes["Include"].Value; } } public static IEnumerable GetExtensionProjectReferences(this XmlDocument csproj) { var nodes = csproj.SelectNodes("//*[local-name() = 'ProjectReference']"); foreach (XmlNode? node in nodes) { if (node?.SelectSingleNode("./*[local-name () = 'IsAppExtension']") != null) { yield return node.Attributes["Include"].Value; } } } public static IEnumerable GetNunitAndXunitTestReferences(this XmlDocument csproj) { var nodes = csproj.SelectNodes("//*[local-name() = 'Reference']"); foreach (XmlNode? node in nodes) { if (node == null) { continue; } var includeValue = node.Attributes["Include"].Value; if (includeValue.EndsWith("_test.dll", StringComparison.Ordinal) || includeValue.EndsWith("_xunit-test.dll", StringComparison.Ordinal)) { yield return includeValue; } } } public static void SetSdk(this XmlDocument csproj, string sdk) { var node = csproj.SelectSingleNode("//*[local-name() = 'Project']"); if (node == null) { throw new Exception($"Could not find a 'Project' node"); } var attrib = node.Attributes["Sdk"]; if (attrib == null) { throw new Exception($"The 'Project' node doesn't have an 'Sdk' attribute"); } attrib.Value = sdk; } public static void SetRuntimeIdentifier(this XmlDocument csproj, string runtimeIdentifier) { var node = csproj.SelectSingleNode("//*[local-name() = 'RuntimeIdentifier']"); if (node == null) { throw new Exception($"Could not find a 'RuntimeIdentifier' node"); } node.InnerText = runtimeIdentifier; } public static void SetProjectReferenceValue(this XmlDocument csproj, string projectInclude, string node, string value) { var nameNode = csproj.SelectSingleNode("//*[local-name() = 'ProjectReference' and @Include = '" + projectInclude + "']/*[local-name() = '" + node + "']"); nameNode.InnerText = value; } public static string? GetAssetTargetFallback(this XmlDocument csproj) => csproj.SelectSingleNode("//*[local-name() = 'AssetTargetFallback']")?.InnerText; public static void SetAssetTargetFallback(this XmlDocument csproj, string value) { var node = csproj.SelectSingleNode("//*[local-name() = 'AssetTargetFallback']"); if (node != null) { node.InnerText = value; } } public static void SetProjectReferenceInclude(this XmlDocument csproj, string projectInclude, string value) { var elements = csproj.SelectElementNodes("ProjectReference"); elements .Where((v) => { var attrib = v.Attributes["Include"]; if (attrib == null) { return false; } return attrib.Value == projectInclude; }) .Single() .Attributes["Include"].Value = value; } public static void CreateProjectReferenceValue(this XmlDocument csproj, string existingInclude, string path, string guid, string name) { var referenceNode = csproj.SelectSingleNode("//*[local-name() = 'Reference' and @Include = '" + existingInclude + "']"); var projectReferenceNode = csproj.CreateElement("ProjectReference", csproj.GetNamespace()); var includeAttribute = csproj.CreateAttribute("Include"); includeAttribute.Value = path.Replace('/', '\\'); projectReferenceNode.Attributes.Append(includeAttribute); var projectNode = csproj.CreateElement("Project", csproj.GetNamespace()); projectNode.InnerText = guid; projectReferenceNode.AppendChild(projectNode); var nameNode = csproj.CreateElement("Name", csproj.GetNamespace()); nameNode.InnerText = name; projectReferenceNode.AppendChild(nameNode); XmlNode itemGroup; if (referenceNode != null) { itemGroup = referenceNode.ParentNode; referenceNode.ParentNode.RemoveChild(referenceNode); } else { itemGroup = csproj.CreateElement("ItemGroup", csproj.GetNamespace()); csproj.SelectSingleNode("//*[local-name() = 'Project']").AppendChild(itemGroup); } itemGroup.AppendChild(projectReferenceNode); } private static XmlNode CreateItemGroup(this XmlDocument csproj) { var lastItemGroup = csproj.SelectSingleNode("//*[local-name() = 'ItemGroup'][last()]"); var newItemGroup = csproj.CreateElement("ItemGroup", csproj.GetNamespace()); lastItemGroup.ParentNode.InsertAfter(newItemGroup, lastItemGroup); return newItemGroup; } public static void AddAdditionalDefines(this XmlDocument csproj, string value) { var mainPropertyGroup = csproj.SelectSingleNode("//*[local-name() = 'PropertyGroup' and not(@Condition)]"); var mainDefine = mainPropertyGroup.SelectSingleNode("*[local-name() = 'DefineConstants']"); if (mainDefine == null) { mainDefine = csproj.CreateElement("DefineConstants", csproj.GetNamespace()); mainDefine.InnerText = value; mainPropertyGroup.AppendChild(mainDefine); } else { mainDefine.InnerText = mainDefine.InnerText + ";" + value; } // make sure all other DefineConstants include the main one var otherDefines = csproj.SelectNodes("//*[local-name() = 'PropertyGroup' and @Condition]/*[local-name() = 'DefineConstants']"); foreach (XmlNode? def in otherDefines) { if (def == null) { continue; } if (!def.InnerText.Contains("$(DefineConstants")) { def.InnerText += ";$(DefineConstants)"; } } } public static void RemoveDefines(this XmlDocument csproj, string defines, string? platform = null, string? configuration = null) { var separator = new char[] { ';' }; var defs = defines.Split(separator, StringSplitOptions.RemoveEmptyEntries); var projnode = csproj.SelectNodes("//*[local-name() = 'PropertyGroup']/*[local-name() = 'DefineConstants']"); foreach (XmlNode? xmlnode in projnode) { if (string.IsNullOrEmpty(xmlnode?.InnerText)) { continue; } var parent = xmlnode.ParentNode; if (!IsNodeApplicable(parent, platform, configuration)) { continue; } string?[] existing = xmlnode.InnerText.Split(separator, StringSplitOptions.RemoveEmptyEntries); var any = false; foreach (var def in defs) { for (var i = 0; i < existing.Length; i++) { if (existing[i] == def) { existing[i] = null; any = true; } } } if (!any) { continue; } xmlnode.InnerText = string.Join(separator[0].ToString(), existing.Where((v) => !string.IsNullOrEmpty(v))); } } public static void AddAdditionalDefines(this XmlDocument csproj, string value, string? platform, string? configuration) { var projnode = csproj.SelectNodes("//*[local-name() = 'PropertyGroup' and @Condition]/*[local-name() = 'DefineConstants']"); foreach (XmlNode? xmlnode in projnode) { if (xmlnode == null) { continue; } var parent = xmlnode.ParentNode; if (parent?.Attributes["Condition"] == null) { continue; } if (!IsNodeApplicable(parent, platform, configuration)) { continue; } if (string.IsNullOrEmpty(xmlnode.InnerText)) { xmlnode.InnerText = value; } else { xmlnode.InnerText += ";" + value; } return; } projnode = csproj.SelectNodes("//*[local-name() = 'PropertyGroup' and @Condition]"); foreach (XmlNode? xmlnode in projnode) { if (xmlnode?.Attributes["Condition"] == null) { continue; } if (!IsNodeApplicable(xmlnode, platform, configuration)) { continue; } var defines = csproj.CreateElement("DefineConstants", csproj.GetNamespace()); defines.InnerText = "$(DefineConstants);" + value; xmlnode.AppendChild(defines); return; } var newPropertyGroup = csproj.AddPropertyGroup(platform, configuration); var defineConstantsElement = csproj.CreateElement("DefineConstants", csproj.GetNamespace()); defineConstantsElement.InnerText = "$(DefineConstants);" + value; newPropertyGroup.AppendChild(defineConstantsElement); } private static XmlNode AddPropertyGroup(this XmlDocument csproj, string? platform, string? configuration) { // Create a new PropertyGroup with the desired condition, and add it just after the last PropertyGroup in the csproj. var projectNode = csproj.SelectSingleNode("//*[local-name() = 'Project']"); var lastPropertyGroup = csproj.SelectNodes("/*[local-name() = 'Project']/*[local-name() = 'PropertyGroup']").Cast().Last(); var newPropertyGroup = csproj.CreateElement("PropertyGroup", csproj.GetNamespace()); if (!string.IsNullOrEmpty(platform) || !string.IsNullOrEmpty(configuration)) { // Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' " var conditionAttribute = csproj.CreateAttribute("Condition"); var left = string.Empty; var right = string.Empty; if (!string.IsNullOrEmpty(configuration)) { left = "$(Configuration)"; right = configuration; } if (!string.IsNullOrEmpty(platform)) { if (!string.IsNullOrEmpty(left)) { left += "|"; right += "|"; } left += "$(Platform)"; right += platform; } conditionAttribute.Value = $"'{left}' == '{right}'"; newPropertyGroup.Attributes.Append(conditionAttribute); } projectNode.InsertAfter(newPropertyGroup, lastPropertyGroup); return newPropertyGroup; } public static void AddTopLevelProperty(this XmlDocument csproj, string property, string value) { var propertyGroup = csproj.SelectNodes("//*[local-name() = 'PropertyGroup' and not(@Condition)]")[0]; var propertyNode = csproj.CreateElement(property, csproj.GetNamespace()); propertyNode.InnerText = value; propertyGroup.AppendChild(propertyNode); } public static void SetNode(this XmlDocument csproj, string node, string value) { var nodes = csproj.SelectNodes("/*/*/*[local-name() = '" + node + "']"); if (nodes.Count == 0) { throw new Exception(string.Format("Could not find node {0}", node)); } foreach (XmlNode? n in nodes) { if (n == null) { continue; } n.InnerText = value; } } public static void RemoveNode(this XmlDocument csproj, string node, bool throwOnInexistentNode = true) { var nodes = csproj.SelectNodes("/*/*/*[local-name() = '" + node + "']"); if (throwOnInexistentNode && nodes.Count == 0) { throw new Exception(string.Format("Could not find node {0}", node)); } foreach (XmlNode? n in nodes) { n?.ParentNode.RemoveChild(n); } } public static void CloneConfiguration(this XmlDocument csproj, string platform, string configuration, string new_configuration) { var projnode = csproj.GetPropertyGroups(platform, configuration); foreach (XmlNode xmlnode in projnode) { var clone = xmlnode.Clone(); var condition = clone.Attributes["Condition"]; condition.InnerText = condition.InnerText.Replace(configuration, new_configuration); xmlnode.ParentNode.InsertAfter(clone, xmlnode); return; } throw new Exception($"Configuration {platform}|{configuration} not found."); } public static void DeleteConfiguration(this XmlDocument csproj, string platform, string configuration) { var projnode = csproj.GetPropertyGroups(platform, configuration); foreach (XmlNode xmlnode in projnode) { xmlnode.ParentNode.RemoveChild(xmlnode); } } private static IEnumerable SelectElementNodes(this XmlNode node, string name) { foreach (XmlNode? child in node.ChildNodes) { if (child == null) { continue; } if (child.NodeType == XmlNodeType.Element && child.Name == name) { yield return child; } if (!child.HasChildNodes) { continue; } foreach (XmlNode descendent in child.SelectElementNodes(name)) { yield return descendent; } } } public static void ResolveAllPaths(this XmlDocument csproj, string project_path, string? rootDirectory = null) { var dir = Path.GetDirectoryName(project_path)!; var nodes_with_paths = new string[] { "AssemblyOriginatorKeyFile", "CodesignEntitlements", "TestLibrariesDirectory", "HintPath", "RootTestsDirectory", }; var attributes_with_paths = new string[][] { new string [] { "None", "Include" }, new string [] { "Compile", "Include" }, new string [] { "Compile", "Exclude" }, new string [] { "ProjectReference", "Include" }, new string [] { "InterfaceDefinition", "Include" }, new string [] { "BundleResource", "Include" }, new string [] { "EmbeddedResource", "Include" }, new string [] { "ImageAsset", "Include" }, new string [] { "GeneratedTestInput", "Include" }, new string [] { "GeneratedTestOutput", "Include" }, new string [] { "TestLibrariesInput", "Include" }, new string [] { "TestLibrariesOutput", "Include" }, new string [] { "Content", "Include" }, new string [] { "ObjcBindingApiDefinition", "Include" }, new string [] { "ObjcBindingCoreSource", "Include" }, new string [] { "ObjcBindingNativeLibrary", "Include" }, new string [] { "ObjcBindingNativeFramework", "Include" }, new string [] { "Import", "Project", "CustomBuildActions.targets", "..\\shared.targets" }, new string [] { "FilesToCopy", "Include" }, new string [] { "FilesToCopyFoo", "Include" }, new string [] { "FilesToCopyFooBar", "Include" }, new string [] { "FilesToCopyEncryptedXml", "Include" }, new string [] { "FilesToCopyCryptographyPkcs", "Include" }, new string [] { "FilesToCopyResources", "Include" }, new string [] { "FilesToCopyXMLFiles", "Include" }, new string [] { "FilesToCopyChannels", "Include" }, new string [] { "CustomMetalSmeltingInput", "Include" }, new string [] { "Metal", "Include" }, new string [] { "NativeReference", "Include" }, }; var nodes_with_variables = new string[] { "MtouchExtraArgs", }; #pragma warning disable IDE0059 // Unnecessary assignment of a value Func? convert = null; #pragma warning restore IDE0059 // Unnecessary assignment of a value convert = (input) => { if (input.Contains(';')) { var split = input.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); for (var i = 0; i < split.Length; i++) { split[i] = convert!.Invoke(split[i]); } return string.Join(";", split); } if (input[0] == '/') { return input; // This is already a full path. } input = input.Replace('\\', '/'); // make unix-style if (rootDirectory != null) { input = input.Replace("$(RootTestsDirectory)", rootDirectory); } // Don't process anything that starts with a variable, it's either a full path already, or the variable will be updated according to the new location if (input.StartsWith("$(", StringComparison.Ordinal)) { return input; } input = Path.GetFullPath(Path.Combine(dir, input)); input = input.Replace('/', '\\'); // make windows-style again return input; }; foreach (var key in nodes_with_paths) { var nodes = csproj.SelectElementNodes(key); foreach (var node in nodes) { node.InnerText = convert(node.InnerText); } } foreach (var key in nodes_with_variables) { var nodes = csproj.SelectElementNodes(key); foreach (var node in nodes) { node.InnerText = node.InnerText.Replace("${ProjectDir}", StringUtils.Quote(System.IO.Path.GetDirectoryName(project_path))); } } foreach (var kvp in attributes_with_paths) { var element = kvp[0]; var attrib = kvp[1]; var nodes = csproj.SelectElementNodes(element); foreach (XmlNode node in nodes) { var a = node.Attributes[attrib]; if (a == null) { continue; } // entries after index 2 is a list of values to filter the attribute value against. var found = kvp.Length == 2; var skipLogicalName = kvp.Length > 2; for (var i = 2; i < kvp.Length; i++) { found |= a.Value == kvp[i]; } if (!found) { continue; } // Fix any default LogicalName values (but don't change existing ones). var ln = node.SelectElementNodes("LogicalName")?.SingleOrDefault(); var links = node.SelectElementNodes("Link"); if (!skipLogicalName && ln == null && !links.Any()) { ln = csproj.CreateElement("LogicalName", csproj.GetNamespace()); node.AppendChild(ln); string logicalName = a.Value; switch (element) { case "BundleResource": if (logicalName.StartsWith("Resources\\", StringComparison.Ordinal)) { logicalName = logicalName.Substring("Resources\\".Length); } break; default: break; } ln.InnerText = logicalName; } a.Value = convert(a.Value); } } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/IXmlResultParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.IO; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public interface IXmlResultParser { (string resultLine, bool failed) ParseXml(TextReader stream, TextWriter? humanReadableOutput); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/NUnitV2ResultParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.IO; using System.Xml; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public class NUnitV2ResultParser : IXmlResultParser { public (string resultLine, bool failed) ParseXml(TextReader stream, TextWriter? humanReadableOutput) { long total, errors, failed, notRun, inconclusive, ignored, skipped, invalid; total = errors = failed = notRun = inconclusive = ignored = skipped = invalid = 0L; var settings = new XmlReaderSettings { ValidationType = ValidationType.None }; using (var reader = XmlReader.Create(stream, settings)) { while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element && reader.Name == "test-results") { long.TryParse(reader["total"], out total); long.TryParse(reader["errors"], out errors); long.TryParse(reader["failures"], out failed); long.TryParse(reader["not-run"], out notRun); long.TryParse(reader["inconclusive"], out inconclusive); long.TryParse(reader["ignored"], out ignored); long.TryParse(reader["skipped"], out skipped); long.TryParse(reader["invalid"], out invalid); } if (humanReadableOutput != null && reader.NodeType == XmlNodeType.Element && reader.Name == "test-suite" && (reader["type"] == "TestFixture" || reader["type"] == "TestCollection")) { var testCaseName = reader["name"]; humanReadableOutput.WriteLine(testCaseName); var time = reader.GetAttribute("time") ?? "0"; // some nodes might not have the time :/ // get the first node and then move in the siblings of the same type reader.ReadToDescendant("test-case"); do { if (reader.Name != "test-case") { break; } // read the test cases in the current node var status = reader["result"]; switch (status) { case "Success": humanReadableOutput.Write("\t[PASS] "); break; case "Ignored": humanReadableOutput.Write("\t[IGNORED] "); break; case "Error": case "Failure": humanReadableOutput.Write("\t[FAIL] "); break; case "Inconclusive": humanReadableOutput.Write("\t[INCONCLUSIVE] "); break; default: humanReadableOutput.Write("\t[INFO] "); break; } humanReadableOutput.Write(reader["name"]); if (status == "Failure" || status == "Error") { // we need to print the message reader.ReadToDescendant("message"); humanReadableOutput.Write($" : {reader.ReadElementContentAsString()}"); reader.ReadToNextSibling("stack-trace"); humanReadableOutput.Write($" : {reader.ReadElementContentAsString()}"); } // add a new line humanReadableOutput.WriteLine(); } while (reader.ReadToNextSibling("test-case")); humanReadableOutput.WriteLine($"{testCaseName} {time} ms"); } } } var passed = total - errors - failed - notRun - inconclusive - ignored - skipped - invalid; var resultLine = $"Tests run: {total} Passed: {passed} Inconclusive: {inconclusive} Failed: {failed + errors} Ignored: {ignored + skipped + invalid}"; humanReadableOutput?.WriteLine(resultLine); return (resultLine, errors != 0 || failed != 0); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/NUnitV2TestReportGenerator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Xml; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public class NUnitV2TestReportGenerator : TestReportGenerator { public override void GenerateTestReport(TextWriter writer, XmlReader reader) { if (reader.Name != "test-results") { if (!reader.ReadToFollowing("test-results")) { return; } } long.TryParse(reader["errors"], out var errors); long.TryParse(reader["failures"], out var failures); if (errors == 0 && failures == 0) { return; } writer.WriteLine("
"); writer.WriteLine("
    "); void write_failure() { var name = reader["name"]; string? message = null; var depth = reader.Depth; if (reader.ReadToDescendant("message")) { message = reader.ReadElementContentAsString(); // ReadToParent while (depth > reader.Depth && reader.Read()) { } } var message_block = message?.IndexOf('\n') >= 0; writer.WriteLine("
  • "); writer.Write(name.AsHtml()); if (!string.IsNullOrEmpty(message)) { writer.Write(": "); if (message_block) { writer.WriteLine("
    "); } writer.Write(message.AsHtml()); if (message_block) { writer.WriteLine("
    "); } } writer.WriteLine("
  • "); } while (reader.ReadToFollowing("test-suite")) { if (reader["type"] == "Assembly") { continue; } var result = reader["result"]; if (result != "Error" && result != "Failure") { continue; } if (result == "Error") { write_failure(); } var depth = reader.Depth; while (reader.Read()) { if (reader.NodeType != XmlNodeType.Element || reader.Name != "test-case") { continue; } result = reader["result"]; if (result == "Error" || result == "Failure") { write_failure(); } if (reader.Depth < depth) { break; } } } writer.WriteLine("
"); writer.WriteLine("
"); } public override void GenerateFailure(XmlWriter writer, string title, string message, TextReader stderr) { writer.WriteStartElement("test-results"); WriteAttributes(writer, ("name", title), ("total", "1"), ("errors", "0"), ("failures", "1"), ("not-run", "0"), ("inconclusive", "0"), ("ignored", "0"), ("skipped", "0"), ("invalid", "0"), ("date", XmlConvert.ToString(DateTime.Now, "yyyy-MM-dd"))); // we are not writting the env and the cunture info since the VSTS uploader does not care writer.WriteStartElement("test-suite"); writer.WriteAttributeString("type", "Assembly"); WriteNUnitV2TestSuiteAttributes(writer, title); writer.WriteStartElement("results"); writer.WriteStartElement("test-suite"); writer.WriteAttributeString("type", "TestFixture"); WriteNUnitV2TestSuiteAttributes(writer, title); writer.WriteStartElement("results"); WriteNUnitV2TestCase(writer, title, message, stderr); writer.WriteEndElement(); // results writer.WriteEndElement(); // test-suite TextFixture writer.WriteEndElement(); // results writer.WriteEndElement(); // test-suite Assembly writer.WriteEndElement(); // test-results } private static void WriteNUnitV2TestSuiteAttributes(XmlWriter writer, string title) => WriteAttributes(writer, ("name", title), ("executed", "True"), ("result", "Failure"), ("success", "False"), ("time", "0"), ("asserts", "1")); private static void WriteNUnitV2TestCase(XmlWriter writer, string title, string message, TextReader stderr) { writer.WriteStartElement("test-case"); WriteAttributes(writer, ("name", title), ("executed", "True"), ("result", "Failure"), ("success", "False"), ("time", "0"), ("asserts", "1") ); writer.WriteStartElement("failure"); writer.WriteStartElement("message"); writer.WriteCDataSafe(message); writer.WriteEndElement(); // message writer.WriteStartElement("stack-trace"); writer.WriteCDataSafe(stderr.ReadToEnd()); writer.WriteEndElement(); // stack-trace writer.WriteEndElement(); // failure writer.WriteEndElement(); // test-case } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/NUnitV3ResultParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Xml; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public class NUnitV3ResultParser : IXmlResultParser { public (string resultLine, bool failed) ParseXml(TextReader source, TextWriter? humanReadableOutput) { long testcasecount, passed, failed, inconclusive, skipped; var failedTestRun = false; // result = "Failed" testcasecount = passed = failed = inconclusive = skipped = 0L; var settings = new XmlReaderSettings { IgnoreWhitespace = true, IgnoreComments = true, IgnoreProcessingInstructions = true }; using (var reader = XmlReader.Create(source, settings)) { while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element && reader.Name == "test-run") { long.TryParse(reader["testcasecount"], out testcasecount); long.TryParse(reader["passed"], out passed); long.TryParse(reader["failed"], out failed); long.TryParse(reader["inconclusive"], out inconclusive); long.TryParse(reader["skipped"], out skipped); failedTestRun = failed != 0; } if (humanReadableOutput != null && reader.NodeType == XmlNodeType.Element && reader.Name == "test-suite") { ParseNUnitV3XmlTestSuite(reader, humanReadableOutput, false); } } } var resultLine = $"Tests run: {testcasecount} Passed: {passed} Inconclusive: {inconclusive} Failed: {failed} Ignored: {skipped + inconclusive}"; humanReadableOutput?.WriteLine(resultLine); return (resultLine, failedTestRun); } private static void ParseNUnitV3XmlTestCase(XmlReader reader, TextWriter humanReadableOutput) { if (reader.NodeType != XmlNodeType.Element) { throw new InvalidOperationException(); } if (reader.Name != "test-case") { throw new InvalidOperationException(); } // read the test cases in the current node var status = reader["result"]; switch (status) { case "Passed": humanReadableOutput.Write("\t[PASS] "); break; case "Skipped": humanReadableOutput.Write("\t[IGNORED] "); break; case "Error": case "Failed": humanReadableOutput.Write("\t[FAIL] "); break; case "Inconclusive": humanReadableOutput.Write("\t[INCONCLUSIVE] "); break; default: humanReadableOutput.Write("\t[INFO] "); break; } var name = reader["name"]; humanReadableOutput.Write(name); if (status == "Failed") { // we need to print the message reader.ReadToDescendant("failure"); reader.ReadToDescendant("message"); humanReadableOutput.Write($" : {reader.ReadElementContentAsString()}"); if (reader.Name != "stack-trace") { reader.ReadToNextSibling("stack-trace"); } humanReadableOutput.Write($" : {reader.ReadElementContentAsString()}"); } if (status == "Skipped") { // nice to have the skip reason reader.ReadToDescendant("reason"); reader.ReadToDescendant("message"); humanReadableOutput.Write($" : {reader.ReadElementContentAsString()}"); } // add a new line humanReadableOutput.WriteLine(); } private static void ParseNUnitV3XmlTestSuite(XmlReader reader, TextWriter humanReadableOutput, bool nested) { if (reader.NodeType != XmlNodeType.Element) { throw new InvalidOperationException(); } if (reader.Name != "test-suite") { throw new InvalidOperationException(); } var type = reader["type"]; var isFixture = type == "TestFixture" || type == "ParameterizedFixture"; var testCaseName = reader["fullname"]; var time = reader.GetAttribute("time") ?? "0"; // some nodes might not have the time :/ if (isFixture) { humanReadableOutput.WriteLine(testCaseName); } if (reader.IsEmptyElement) { if (isFixture) { humanReadableOutput.WriteLine($"{testCaseName} {time} ms"); } return; } while (reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: if (reader.Name == "test-case") { ParseNUnitV3XmlTestCase(reader, humanReadableOutput); } else if (reader.Name == "test-suite") { ParseNUnitV3XmlTestSuite(reader, humanReadableOutput, nested || isFixture); } break; case XmlNodeType.EndElement: if (reader.Name == "test-suite") { if (isFixture) { humanReadableOutput.WriteLine($"{testCaseName} {time} ms"); } return; } break; } } throw new InvalidOperationException("Invalid XML: no test-suite end element"); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/NUnitV3TestReportGenerator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Xml; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public class NUnitV3TestReportGenerator : TestReportGenerator { public override void GenerateTestReport(TextWriter writer, XmlReader reader) { var failedTests = new List<(string name, string message)>(); while (reader.ReadToFollowing("test-case")) { var status = reader["result"]; switch (status) { case "Error": case "Failed": string name = reader["fullname"] ?? throw new InvalidOperationException(); var subtree = reader.ReadSubtree(); subtree.ReadToDescendant("message"); string message = subtree.ReadElementContentAsString(); while (subtree.Read()) { } // read to end of subtree failedTests.Add((name, message)); break; } } if (failedTests.Count > 0) { writer.WriteLine("
"); writer.WriteLine("
    "); foreach (var (name, message) in failedTests) { writer.WriteLine("
  • "); writer.Write(name.AsHtml()); if (!string.IsNullOrEmpty(message)) { writer.Write(": "); writer.Write(message.AsHtml()); } writer.WriteLine("
    "); writer.WriteLine("
  • "); } writer.WriteLine("
"); writer.WriteLine("
"); } } public override void GenerateFailure(XmlWriter writer, string title, string message, TextReader stderr) { var date = DateTime.Now; writer.WriteStartElement("test-run"); // defualt values for the crash WriteAttributes(writer, ("name", title), ("testcasecount", "1"), ("result", "Failed"), ("time", "0"), ("total", "1"), ("passed", "0"), ("failed", "1"), ("inconclusive", "0"), ("skipped", "0"), ("asserts", "1"), ("run-date", XmlConvert.ToString(date, "yyyy-MM-dd")), ("start-time", date.ToString("HH:mm:ss")) ); writer.WriteStartElement("test-suite"); writer.WriteAttributeString("type", "Assembly"); WriteNUnitV3TestSuiteAttributes(writer, title); WriteFailure(writer, "Child test failed"); writer.WriteStartElement("test-suite"); WriteAttributes(writer, ("name", title), ("fullname", title), ("type", "TestFixture"), ("testcasecount", "1"), ("result", "Failed"), ("time", "0"), ("total", "1"), ("passed", "0"), ("failed", "1"), ("inconclusive", "0"), ("skipped", "0"), ("asserts", "1")); writer.WriteStartElement("test-case"); WriteAttributes(writer, ("id", "1"), ("name", title), ("fullname", title), ("result", "Failed"), ("time", "0"), ("asserts", "1")); WriteFailure(writer, message, stderr); writer.WriteEndElement(); // test-case writer.WriteEndElement(); // test-suite = TestFixture writer.WriteEndElement(); // test-suite = Assembly writer.WriteEndElement(); // test-run } private static void WriteNUnitV3TestSuiteAttributes(XmlWriter writer, string title) => WriteAttributes(writer, ("id", "1"), ("name", title), ("testcasecount", "1"), ("result", "Failed"), ("time", "0"), ("total", "1"), ("passed", "0"), ("failed", "1"), ("inconclusive", "0"), ("skipped", "0"), ("asserts", "0")); } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/TestReportGenerator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.IO; using System.Xml; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public interface ITestReportGenerator { void GenerateTestReport(TextWriter writer, XmlReader reader); void GenerateFailure(XmlWriter writer, string title, string message, TextReader stderr); } public abstract class TestReportGenerator : ITestReportGenerator { public abstract void GenerateFailure(XmlWriter writer, string title, string message, TextReader stderr); public abstract void GenerateTestReport(TextWriter writer, XmlReader reader); protected static void WriteAttributes(XmlWriter writer, params (string name, string data)[] attrs) { foreach (var (name, data) in attrs) { writer.WriteAttributeString(name, data); } } protected static void WriteFailure(XmlWriter writer, string message, TextReader? stderr = null) { writer.WriteStartElement("failure"); writer.WriteStartElement("message"); writer.WriteCDataSafe(message); writer.WriteEndElement(); // message if (stderr != null) { writer.WriteStartElement("stack-trace"); writer.WriteCDataSafe(stderr.ReadToEnd()); writer.WriteEndElement(); //stack trace } writer.WriteEndElement(); // failure } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/TouchUnitResultParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.IO; using System.Xml; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public class TouchUnitResultParser : IXmlResultParser { public (string resultLine, bool failed) ParseXml(TextReader stream, TextWriter? humanReadableOutput) { long total, errors, failed, notRun, inconclusive, ignored, skipped, invalid; total = errors = failed = notRun = inconclusive = ignored = skipped = invalid = 0L; using (var reader = XmlReader.Create(stream)) { while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element && reader.Name == "test-results") { long.TryParse(reader["total"], out total); long.TryParse(reader["errors"], out errors); long.TryParse(reader["failures"], out failed); long.TryParse(reader["not-run"], out notRun); long.TryParse(reader["inconclusive"], out inconclusive); long.TryParse(reader["ignored"], out ignored); long.TryParse(reader["skipped"], out skipped); long.TryParse(reader["invalid"], out invalid); } if (humanReadableOutput != null && reader.NodeType == XmlNodeType.Element && reader.Name == "TouchUnitExtraData") { // move fwd to get to the CData if (reader.Read()) { humanReadableOutput.Write(reader.Value); } } } } var passed = total - errors - failed - notRun - inconclusive - ignored - skipped - invalid; var resultLine = $"Tests run: {total} Passed: {passed} Inconclusive: {inconclusive} Failed: {failed + errors} Ignored: {ignored + skipped + invalid}"; humanReadableOutput?.WriteLine(resultLine); return (resultLine, errors != 0 || failed != 0); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/TouchUnitTestReportGenerator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Xml; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public class TouchUnitTestReportGenerator : TestReportGenerator { public override void GenerateFailure(XmlWriter writer, string title, string message, TextReader stderr) { // No-op - there was no implementation when we were splitting the parser up } public override void GenerateTestReport(TextWriter writer, XmlReader reader) { while (reader.Read()) { if (reader.NodeType != XmlNodeType.Element) { continue; } if (reader.Name == "test-run") { var innerReader = new NUnitV3TestReportGenerator(); innerReader.GenerateTestReport(writer, reader); return; } if (reader.Name == "test-results") { var innerReader = new NUnitV2TestReportGenerator(); innerReader.GenerateTestReport(writer, reader); return; } } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/TrxResultParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public class TrxResultParser : IXmlResultParser { public class TrxTests : List { public long Total; public long Executed; public long Passed; public long Failed; public long Error; public long Timeout; public long Aborted; public long Inconclusive; public long NotRunnable; public long NotExecuted; } public class TrxTest { public string? Outcome; public string? ClassName; public string? TestName; public TimeSpan? Duration; public string? Message; } public (string resultLine, bool failed) ParseXml(TextReader stream, TextWriter? humanReadableOutput) { using var reader = XmlReader.Create(stream); var tests = ParseTrxXml(reader); var resultLine = $"Tests run: {tests.Total} Passed: {tests.Passed} Inconclusive: {tests.Inconclusive} Failed: {tests.Failed + tests.Error} Ignored: {tests.NotRunnable}"; if (humanReadableOutput != null) { foreach (var groupedByClass in tests.GroupBy(v => v.ClassName).OrderBy(v => v.Key)) { var className = groupedByClass.Key; var totalDuration = TimeSpan.FromTicks(groupedByClass.Select(v => v.Duration?.Ticks ?? 0).Sum()); humanReadableOutput.WriteLine(className); foreach (var test in groupedByClass) { humanReadableOutput.Write('\t'); switch (test.Outcome) { case "Passed": humanReadableOutput.Write("[PASS]"); break; default: humanReadableOutput.Write($"[UNKNOWN ({test.Outcome})]"); break; } humanReadableOutput.Write(' '); humanReadableOutput.Write(test.TestName); humanReadableOutput.Write(": "); humanReadableOutput.Write(test.Duration?.ToString()); humanReadableOutput.WriteLine(); } humanReadableOutput.WriteLine($"{className} {totalDuration}"); } humanReadableOutput.WriteLine(resultLine); } return (resultLine, !(tests.Error == 0 && tests.Aborted == 0 && tests.Timeout == 0 && tests.Failed == 0)); } public static TrxTests ParseTrxXml(XmlReader reader) { var rv = new TrxTests(); var tests = new Dictionary(); TrxTest? lastTest = null; while (reader.Read()) { if (reader.NodeType != XmlNodeType.Element) { continue; } switch (reader.Name) { case "Counters": long.TryParse(reader["total"], out rv.Total); long.TryParse(reader["executed"], out rv.Executed); long.TryParse(reader["passed"], out rv.Passed); long.TryParse(reader["failed"], out rv.Failed); long.TryParse(reader["error"], out rv.Error); long.TryParse(reader["timeout"], out rv.Timeout); long.TryParse(reader["aborted"], out rv.Aborted); long.TryParse(reader["inconclusive"], out rv.Inconclusive); long.TryParse(reader["notRunnable"], out rv.NotRunnable); long.TryParse(reader["notExecuted"], out rv.NotExecuted); break; case "UnitTestResult": { var testId = reader["testId"]; var outcome = reader["outcome"]; var test = new TrxTest { Outcome = outcome }; if (TimeSpan.TryParse(reader["duration"], out var duration)) { test.Duration = duration; } tests[testId] = test; rv.Add(test); lastTest = test; break; } case "Message": if (lastTest != null) { reader.Read(); lastTest.Message = reader.Value; } break; case "UnitTest": { var id = reader["id"]; var test = tests[id]; while (reader.Read() && !(reader.NodeType == XmlNodeType.Element && reader.Name == "TestMethod")) { ; } test.ClassName = reader["className"]; test.TestName = reader["name"]; break; } default: break; } } return rv; } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/TrxTestReportGenerator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.IO; using System.Linq; using System.Xml; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public class TrxTestReportGenerator : TestReportGenerator { public override void GenerateFailure(XmlWriter writer, string title, string message, TextReader stderr) { // No-op - there was no implementation when we were splitting the parser up } public override void GenerateTestReport(TextWriter writer, XmlReader reader) { var tests = TrxResultParser.ParseTrxXml(reader); var failedTests = tests.Where(v => v.Outcome != "Passed" && v.Outcome != "NotExecuted"); if (failedTests.Any()) { writer.WriteLine("
"); writer.WriteLine("
    "); foreach (var test in failedTests) { writer.WriteLine("
  • "); writer.Write($"{test.ClassName?.AsHtml()}.{test.TestName?.AsHtml()}"); if (!string.IsNullOrEmpty(test.Message)) { writer.Write(": "); writer.Write(test.Message?.AsHtml()); } writer.WriteLine("
    "); writer.WriteLine("
  • "); } writer.WriteLine("
"); writer.WriteLine("
"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/XUnitResultParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.IO; using System.Xml; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public class XUnitResultParser : IXmlResultParser { public (string resultLine, bool failed) ParseXml(TextReader stream, TextWriter? humanReadableOutput) { long total, errors, failed, notRun, inconclusive, ignored, skipped, invalid; total = errors = failed = notRun = inconclusive = ignored = skipped = invalid = 0L; using (var reader = XmlReader.Create(stream)) { while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element && reader.Name == "assembly") { long.TryParse(reader["total"], out var assemblyCount); total += assemblyCount; long.TryParse(reader["errors"], out var assemblyErrors); errors += assemblyErrors; long.TryParse(reader["failed"], out var assemblyFailures); failed += assemblyFailures; long.TryParse(reader["skipped"], out var assemblySkipped); skipped += assemblySkipped; } if (humanReadableOutput != null && reader.NodeType == XmlNodeType.Element && reader.Name == "collection") { var testCaseName = reader["name"].Replace("Test collection for ", ""); humanReadableOutput.WriteLine(testCaseName); var time = reader.GetAttribute("time") ?? "0"; // some nodes might not have the time :/ // get the first node and then move in the siblings of the same type reader.ReadToDescendant("test"); do { if (reader.Name != "test") { break; } // read the test cases in the current node var status = reader["result"]; switch (status) { case "Pass": humanReadableOutput.Write("\t[PASS] "); break; case "Skip": humanReadableOutput.Write("\t[IGNORED] "); break; case "Fail": humanReadableOutput.Write("\t[FAIL] "); break; default: humanReadableOutput.Write("\t[FAIL] "); break; } humanReadableOutput.Write(reader["name"]); if (status == "Fail") { // we need to print the message reader.ReadToDescendant("message"); humanReadableOutput.Write($" : {reader.ReadElementContentAsString()}"); reader.ReadToNextSibling("stack-trace"); humanReadableOutput.Write($" : {reader.ReadElementContentAsString()}"); } // add a new line humanReadableOutput.WriteLine(); } while (reader.ReadToNextSibling("test")); humanReadableOutput.WriteLine($"{testCaseName} {time} ms"); } } } var passed = total - errors - failed - notRun - inconclusive - ignored - skipped - invalid; var resultLine = $"Tests run: {total} Passed: {passed} Inconclusive: {inconclusive} Failed: {failed + errors} Ignored: {ignored + skipped + invalid}"; humanReadableOutput?.WriteLine(resultLine); return (resultLine, errors != 0 || failed != 0); } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/XUnitTestReportGenerator.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Xml; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public class XUnitTestReportGenerator : TestReportGenerator { public override void GenerateFailure(XmlWriter writer, string title, string message, TextReader stderr) { writer.WriteStartElement("assemblies"); writer.WriteStartElement("assembly"); WriteAttributes(writer, ("name", title), ("environment", "64-bit .NET Standard [collection-per-class, non-parallel]"), ("test-framework", "xUnit.net 2.4.1.0"), ("run-date", XmlConvert.ToString(DateTime.Now, "yyyy-MM-dd")), ("total", "1"), ("passed", "0"), ("failed", "1"), ("skipped", "0"), ("time", "0"), ("errors", "0")); writer.WriteStartElement("collection"); WriteAttributes(writer, ("total", "1"), ("passed", "0"), ("failed", "1"), ("skipped", "0"), ("name", title), ("time", "0")); writer.WriteStartElement("test"); WriteAttributes(writer, ("name", title), ("type", "TestApp"), ("method", "Run"), ("time", "0"), ("result", "Fail")); WriteFailure(writer, message, stderr); writer.WriteEndElement(); // test writer.WriteEndElement(); // collection writer.WriteEndElement(); // assembly writer.WriteEndElement(); // assemblies } public override void GenerateTestReport(TextWriter writer, XmlReader reader) { var failedTests = new List<(string name, string message)>(); // xUnit is not as nice and does not provide the final result in a top node, // we need to look in all the collections and find all the failed tests, this is really bad :/ while (reader.Read()) { if (reader.NodeType != XmlNodeType.Element || reader.Name != "collection") { continue; } reader.ReadToDescendant("test"); do { if (reader.Name != "test") { break; } // read the test cases in the current node var status = reader["result"]; switch (status) { case "Fail": string name = reader["name"] ?? throw new InvalidOperationException(); reader.ReadToDescendant("message"); string message = reader.ReadElementContentAsString(); failedTests.Add((name, message)); break; } } while (reader.ReadToNextSibling("test")); } if (failedTests.Count > 0) { writer.WriteLine("
"); writer.WriteLine("
    "); foreach (var (name, message) in failedTests) { writer.WriteLine("
  • "); writer.Write(name.AsHtml()); if (!string.IsNullOrEmpty(message)) { writer.Write(": "); writer.Write(message.AsHtml()); } writer.WriteLine("
    "); writer.WriteLine("
  • "); } writer.WriteLine("
"); writer.WriteLine("
"); } } } ================================================ FILE: src/Microsoft.DotNet.XHarness.iOS.Shared/XmlResults/XmlResultParser.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml; using System.Xml.Linq; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; #nullable enable namespace Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; public class XmlResultParser : IResultParser { private static readonly IHelpers s_helpers = new Helpers(); private readonly Dictionary _xmlFormatters = new() { { XmlResultJargon.TouchUnit_NUnitV2, (new TouchUnitResultParser(), new TouchUnitTestReportGenerator()) }, { XmlResultJargon.TouchUnit_NUnitV3, (new TouchUnitResultParser(), new TouchUnitTestReportGenerator()) }, { XmlResultJargon.NUnitV2, (new NUnitV2ResultParser(), new NUnitV2TestReportGenerator()) }, { XmlResultJargon.NUnitV3, (new NUnitV3ResultParser(), new NUnitV3TestReportGenerator()) }, { XmlResultJargon.Trx, (new TrxResultParser(), new TrxTestReportGenerator()) }, { XmlResultJargon.xUnit, (new XUnitResultParser(), new XUnitTestReportGenerator()) }, }; // test if the file is valid xml, or at least, that can be read it. public bool IsValidXml(string path, out XmlResultJargon type) { type = XmlResultJargon.Missing; if (!File.Exists(path)) { return false; } using var stream = File.OpenText(path); return IsValidXml(stream, out type); } // test if the file is valid xml, or at least, that can be read it. public bool IsValidXml(TextReader stream, out XmlResultJargon type) { type = XmlResultJargon.Missing; string? line; while ((line = stream.ReadLine()) != null) { // special case when get got the tcp connection if (line.Contains("ping")) { continue; } if (line.Contains("test-run")) { // first element of the NUnitV3 test collection type = XmlResultJargon.NUnitV3; return true; } if (line.Contains("TouchUnitTestRun")) { while ((line = stream.ReadLine()) != null) { if (line.Contains("test-run")) { type = XmlResultJargon.TouchUnit_NUnitV3; return true; } if (line.Contains("test-results")) { type = XmlResultJargon.TouchUnit_NUnitV2; return true; } } return false; } if (line.Contains("test-results")) { // first element of the NUnitV3 test collection type = XmlResultJargon.NUnitV2; return true; } if (line.Contains("")) { // first element of the xUnit test collection type = XmlResultJargon.xUnit; return true; } if (line.Contains("Could not parse {xmlType}: Not supported format.
"); } } // get the file, parse it and add the attachments to the first node found public void UpdateMissingData(string source, string destination, string applicationName, IEnumerable attachments) { // we could do this with a XmlReader and a Writer, but might be to complicated to get right, we pay with performance what we // cannot pay with brain cells. var doc = XDocument.Load(source); var attachmentsElement = new XElement("attachments"); foreach (var path in attachments) { // we do not add a description, VSTS ignores that :/ attachmentsElement.Add(new XElement("attachment", new XElement("filePath", path))); } var testSuitesElements = doc.Descendants().Where(e => e.Name == "test-suite" && e.Attribute("type")?.Value == "Assembly"); if (!testSuitesElements.Any()) { return; } // add the attachments to the first test-suite, this will add the attachmnets to it, which will be added to the test-run, the pipeline // SHOULD NOT merge runs, else this upload will be really hard to use. Also, just to one of them, else we have duplicated logs. testSuitesElements.FirstOrDefault().Add(attachmentsElement); foreach (var suite in testSuitesElements) { suite.SetAttributeValue("name", applicationName); suite.SetAttributeValue("fullname", applicationName); // docs say just name, but I've seen the fullname instead, docs usually lie // add also the attachments to all the failing tests, this will make the life of the person monitoring easier, since // he will see the logs directly from the attachment page var tests = suite.Descendants().Where(e => e.Name == "test-case" && e.Attribute("result").Value == "Failed"); foreach (var t in tests) { t.Add(attachmentsElement); } } doc.Save(destination); } private void GenerateFailureXml(string destination, string title, string message, TextReader stderrReader, XmlResultJargon jargon) { var settings = new XmlWriterSettings { Indent = true }; using (var stream = File.CreateText(destination)) using (var xmlWriter = XmlWriter.Create(stream, settings)) { xmlWriter.WriteStartDocument(); if (_xmlFormatters.TryGetValue(jargon, out var xmlFormatters)) { xmlFormatters.Generator.GenerateFailure(xmlWriter, title, message, stderrReader); } xmlWriter.WriteEndDocument(); } } public void GenerateFailure(ILogs logs, string source, string appName, string? variation, string title, string message, string stderrPath, XmlResultJargon jargon) { using var stderrReader = new StreamReader(stderrPath); GenerateFailure(logs, source, appName, variation, title, message, stderrReader, jargon); } public void GenerateFailure(ILogs logs, string source, string appName, string? variation, string title, string message, TextReader stderrReader, XmlResultJargon jargon) { // VSTS does not provide a nice way to report build errors, create a fake // test result with a failure in the case the build did not work var failureLogXml = logs.Create($"vsts-nunit-{source}-{s_helpers.Timestamp}.xml", LogType.XmlLog.ToString()); if (jargon == XmlResultJargon.NUnitV3) { var failureXmlTmp = logs.Create($"nunit-{source}-{s_helpers.Timestamp}.tmp", "Failure Log tmp"); GenerateFailureXml(failureXmlTmp.FullPath, title, message, stderrReader, jargon); // add the required attachments and the info of the application that failed to install var failure_logs = Directory.GetFiles(logs.Directory).Where(p => !p.Contains("nunit")); // all logs but ourself UpdateMissingData(failureXmlTmp.FullPath, failureLogXml.FullPath, $"{appName} {variation}", failure_logs); } else { GenerateFailureXml(failureLogXml.FullPath, title, message, stderrReader, jargon); } } public static string GetVSTSFilename(string filename) { if (filename == null) { throw new ArgumentNullException(nameof(filename)); } var dirName = Path.GetDirectoryName(filename); return dirName == null ? $"vsts-{Path.GetFileName(filename)}" : Path.Combine(dirName, $"vsts-{Path.GetFileName(filename)}"); } } ================================================ FILE: tests/Directory.Build.targets ================================================ ================================================ FILE: tests/Microsoft.DotNet.XHarness.Android.Tests/AdbRunnerLogFilterTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Xunit; namespace Microsoft.DotNet.XHarness.Android.Tests; public class AdbRunnerLogFilterTests { [Fact] public void FilterToDotnetLines_FiltersCorrectly() { var logcat = string.Join('\n', new[] { "03-24 21:14:29.731 15103 17813 I EuiccGoogle: some noise", "03-24 21:14:30.240 17856 17873 I DOTNET : Extracting asset to /data/user/0/net.dot.Tests/files/System.Linq.dll", "03-24 21:14:30.247 16176 17499 E SpeechMicro: Hotword model is single-channel neuralnet.", "03-24 21:15:34.276 17856 25041 I DOTNET : === TEST EXECUTION SUMMARY ===", "03-24 21:15:34.276 17856 25041 I DOTNET : Tests run: 2686 Passed: 2247 Failed: 26", "03-24 21:15:34.281 17856 17873 D DOTNET : Exit code: 1.", }); var result = AdbRunner.FilterToDotnetLines(logcat); Assert.Contains("Extracting asset", result); Assert.Contains("TEST EXECUTION SUMMARY", result); Assert.Contains("Tests run: 2686", result); Assert.Contains("Exit code: 1", result); Assert.DoesNotContain("EuiccGoogle", result); Assert.DoesNotContain("SpeechMicro", result); } [Fact] public void FilterToDotnetLines_EmptyInput_ReturnsEmpty() { Assert.Equal(string.Empty, AdbRunner.FilterToDotnetLines("")); Assert.Equal(string.Empty, AdbRunner.FilterToDotnetLines(null!)); } [Fact] public void FilterToDotnetLines_NoDotnetLines_ReturnsEmpty() { var logcat = "03-24 21:14:29.731 15103 17813 I EuiccGoogle: noise\n03-24 21:14:29.731 15103 17813 I Finsky: more noise"; var result = AdbRunner.FilterToDotnetLines(logcat); Assert.Equal(string.Empty, result); } [Fact] public void FilterToDotnetLines_HandlesWindowsLineEndings() { var logcat = "noise line\r\n03-24 21:15:34.276 17856 25041 I DOTNET : test output\r\nmore noise\r\n"; var result = AdbRunner.FilterToDotnetLines(logcat); Assert.Contains("DOTNET : test output", result); Assert.DoesNotContain("noise line", result); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Android.Tests/AdbRunnerTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using Microsoft.DotNet.XHarness.Android.Execution; using Microsoft.DotNet.XHarness.Common.Utilities; using Microsoft.Extensions.Logging; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Android.Tests; public class AdbRunnerTests : IDisposable { private static readonly string s_scratchAndOutputPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); private static readonly string s_adbPath = Path.Combine(s_scratchAndOutputPath, "adb"); private static string s_currentDeviceSerial = ""; private static int s_bootCompleteCheckTimes = 0; private static int s_devicesCallCount = 0; private static int s_devicesReturnEmptyForNFirstCalls = 0; private readonly Mock _mainLog; private readonly Mock _processManager; private readonly List _fakeDeviceList; public AdbRunnerTests() { _mainLog = new Mock(); _processManager = new Mock(); // Fake devices to pretend are attached to the system _fakeDeviceList = InitializeFakeDeviceList(); // Reset call counters for each test s_devicesCallCount = 0; s_devicesReturnEmptyForNFirstCalls = 0; // Fake ADB executable since its path is checked Directory.CreateDirectory(s_scratchAndOutputPath); File.WriteAllText(s_adbPath, string.Empty); // Mock to check the args ADB actually gets called with _processManager.Setup(pm => pm.Run( It.IsAny(), // process, not checking the value to match any call It.IsAny>(), // same It.IsAny())).Returns((string p, IEnumerable a, TimeSpan t) => CallFakeProcessManager(p, a.ToArray(), t)); } public void Dispose() { Directory.Delete(s_scratchAndOutputPath, true); GC.SuppressFinalize(this); } #region Tests [Fact] public void GetAdbState() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); string result = runner.GetAdbState(); VerifyAdbCall("get-state"); Assert.Equal("device", result); } [Fact] public void ClearAdbLog() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); runner.ClearAdbLog(); VerifyAdbCall("logcat", "-b", "all", "-c"); } [Fact] public void DumpAdbLog() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); string pathToDumpLogTo = Path.Join(s_scratchAndOutputPath, $"{Path.GetRandomFileName()}.log"); runner.TryDumpAdbLog(pathToDumpLogTo); VerifyAdbCall("logcat", "-d", ""); Assert.Equal("Sample LogCat Output", File.ReadAllText(pathToDumpLogTo)); } [Fact] public void DumpBugReport() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); string pathToDumpBugReport = Path.Join(s_scratchAndOutputPath, Path.GetRandomFileName()); runner.GetDevice(requiredDeviceId: _fakeDeviceList.First().DeviceSerial); runner.DumpBugReport(pathToDumpBugReport); VerifyAdbCall("bugreport", $"{pathToDumpBugReport}.zip"); Assert.Equal("Sample BugReport Output", File.ReadAllText($"{pathToDumpBugReport}.zip")); } [Fact] public void WaitForDevice() { s_bootCompleteCheckTimes = 0; // Force simulating device is offline var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); string fakeDeviceName = $"emulator-{new Random().Next(9999)}"; runner.SetActiveDevice(new AndroidDevice(fakeDeviceName)); runner.WaitForDevice(); s_bootCompleteCheckTimes = 0; // Force simulating device is offline runner.SetActiveDevice(null); runner.WaitForDevice(); VerifyAdbCall(Times.Exactly(2), "wait-for-device"); VerifyAdbCall(Times.Exactly(2), "-s", fakeDeviceName, "shell", "getprop", "sys.boot_completed"); VerifyAdbCall(Times.Exactly(2), "shell", "getprop", "sys.boot_completed"); } [Fact] public void ListDevicesAndArchitectures() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); var result = runner.GetDevices(); VerifyAdbCall("devices", "-l"); // Ensure it called, parsed the four random device names and found all four architectures foreach (var fakeDevice in _fakeDeviceList) { VerifyAdbCall("-s", fakeDevice.DeviceSerial, "shell", "getprop", "ro.product.cpu.abilist"); Assert.Equal(fakeDevice.SupportedArchitectures, result.Single(d => d.DeviceSerial == fakeDevice.DeviceSerial).SupportedArchitectures); VerifyAdbCall("-s", fakeDevice.DeviceSerial, "shell", "getprop", "ro.build.version.sdk"); Assert.Equal(fakeDevice.ApiVersion, result.Single(d => d.ApiVersion == fakeDevice.ApiVersion).ApiVersion); VerifyAdbCall("-s", fakeDevice.DeviceSerial, "shell", "getprop", "ro.product.cpu.abi"); Assert.Equal(fakeDevice.Architecture, result.Single(d => d.DeviceSerial == fakeDevice.DeviceSerial).Architecture); } Assert.Equal(4, result.Count); } [Fact] public void StartAdbServer() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); runner.StartAdbServer(); VerifyAdbCall("start-server"); } [Fact] public void KillAdbServer() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); runner.KillAdbServer(); VerifyAdbCall("kill-server"); } [Fact] public void InstallApk() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); string fakeApkPath = Path.Join(s_scratchAndOutputPath, $"{Path.GetRandomFileName()}.apk"); File.Create(fakeApkPath).Close(); int exitCode = runner.InstallApk(fakeApkPath); VerifyAdbCall("install", fakeApkPath); Assert.Equal(0, exitCode); } [Fact] public void UninstallApk() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); string fakeApkName = $"{Path.GetRandomFileName()}"; int exitCode = runner.UninstallApk(fakeApkName); VerifyAdbCall("uninstall", fakeApkName); Assert.Equal(0, exitCode); } [Fact] public void KillApk() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); string fakeApkName = $"{Path.GetRandomFileName()}"; int exitCode = runner.KillApk(fakeApkName); VerifyAdbCall("shell", "am", "kill", "--user", "all", fakeApkName); Assert.Equal(0, exitCode); } [Fact] public void GetDevice() { var requiredArchitecture = "x86_64"; var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); var result = runner.GetDevice(requiredArchitectures: new[] { requiredArchitecture }); VerifyAdbCall("devices", "-l"); Assert.Contains(_fakeDeviceList, d => d.DeviceSerial == result.DeviceSerial); } [Fact] public void GetDeviceWithArchitecture() { var requiredArchitecture = "x86"; var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); var result = runner.GetDevice(loadArchitecture: true, requiredArchitectures: new[] { requiredArchitecture }); VerifyAdbCall("devices", "-l"); VerifyAdbCall("-s", result.DeviceSerial, "shell", "getprop", "ro.product.cpu.abi"); Assert.Contains(_fakeDeviceList, d => d.DeviceSerial == result.DeviceSerial && d.Architecture == result.Architecture); } [Fact] public void GetDeviceWithApiVersion() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); var device = _fakeDeviceList.Single(d => d.ApiVersion == 30); var result = runner.GetDevice(loadArchitecture: true, requiredApiVersion: 30); VerifyAdbCall("devices", "-l"); Assert.Equal(device.DeviceSerial, result.DeviceSerial); Assert.Equal(device.ApiVersion, result.ApiVersion); Assert.Equal(device.Architecture, result.Architecture); } [Fact] public void GetDeviceWithAppAndApiVersion() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); var device = _fakeDeviceList.Single(d => d.ApiVersion == 31 && d.InstalledApplications.Contains("net.dot.E")); var result = runner.GetDevice(requiredInstalledApp: "net.dot.E", requiredApiVersion: 31); VerifyAdbCall("devices", "-l"); Assert.Equal(device.DeviceSerial, result.DeviceSerial); Assert.Equal(device.ApiVersion, result.ApiVersion); } [Fact] public void RebootAndroidDevice() { var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); runner.RebootAndroidDevice(); VerifyAdbCall("reboot"); } [Theory] [InlineData(null)] [InlineData("")] [InlineData("FakeInstrumentationName")] public void RunInstrumentation(string instrumentationName) { string fakeApkName = Path.GetRandomFileName(); var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); ProcessExecutionResults result; var fakeArgs = new Dictionary() { { "arg1", "value1" }, { "arg2", "value2" } }; result = runner.RunApkInstrumentation(fakeApkName, instrumentationName, fakeArgs, TimeSpan.FromSeconds(123)); Assert.Equal(0, result.ExitCode); result = runner.RunApkInstrumentation(fakeApkName, instrumentationName, new Dictionary(), TimeSpan.FromSeconds(456)); Assert.Equal(0, result.ExitCode); if (string.IsNullOrEmpty(instrumentationName)) { VerifyAdbCall("shell", "am", "instrument", "-e", "arg1", "value1", "-e", "arg2", "value2", "-w", fakeApkName); VerifyAdbCall("shell", "am", "instrument", "-w", fakeApkName); } else { VerifyAdbCall("shell", "am", "instrument", "-e", "arg1", "value1", "-e", "arg2", "value2", "-w", $"{fakeApkName}/{instrumentationName}"); VerifyAdbCall("shell", "am", "instrument", "-w", $"{fakeApkName}/{instrumentationName}"); } } [Fact] public void TryRecoverEmulator_WhenDeviceAppearsAfterAdbReset_ReturnsTrue() { // The first devices call (diagnostics) returns empty; after ADB reset (kill+start-server), devices reappear s_devicesReturnEmptyForNFirstCalls = 1; s_bootCompleteCheckTimes = 1; // Boot is already complete, no extra wait needed var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); bool result = runner.TryRecoverEmulator(); Assert.True(result); VerifyAdbCall(Times.AtLeast(2), "devices", "-l"); VerifyAdbCall(Times.Once(), "kill-server"); VerifyAdbCall(Times.Once(), "start-server"); } [Fact] public void TryRecoverEmulator_WhenDeviceAppearsAfterEmulatorRestart_ReturnsTrue() { // The first two devices calls return empty (diagnostics + post-ADB-reset recheck); // the emulator restart (emu restart fallback) succeeds, and the device appears on the next check. s_devicesReturnEmptyForNFirstCalls = 2; s_bootCompleteCheckTimes = 1; // Boot is already complete, no extra wait needed var runner = new AdbRunner(_mainLog.Object, _processManager.Object, s_adbPath); bool result = runner.TryRecoverEmulator(); Assert.True(result); VerifyAdbCall(Times.AtLeast(3), "devices", "-l"); VerifyAdbCall(Times.Once(), "kill-server"); VerifyAdbCall(Times.Once(), "start-server"); } #endregion #region Helper Functions // Generates a list of fake devices, one per supported architecture so we can test AdbRunner's parsing of the output. // As with most of these tests, if adb.exe changes, this will break (we are locked into specific version) private static List InitializeFakeDeviceList() { var r = new Random(); return new List { new AndroidDevice($"somedevice-{r.Next(9999)}") { ApiVersion = 29, Architecture = "x86_64", SupportedArchitectures = new[] { "x86_64", "x86" }, InstalledApplications = new[] { "net.dot.A", "net.dot.B" } }, new AndroidDevice($"somedevice-{r.Next(9999)}") { ApiVersion = 30, Architecture = "x86", SupportedArchitectures = new[] { "x86" }, InstalledApplications = new[] { "net.dot.C", "net.dot.D" } }, new AndroidDevice($"emulator-{r.Next(9999)}") { ApiVersion = 31, Architecture = "arm64-v8a", SupportedArchitectures = new[] { "arm64-v8a", "x86_64", "x86" }, InstalledApplications = new[] { "net.dot.E", "net.dot.F" } }, new AndroidDevice($"emulator-{r.Next(9999)}") { ApiVersion = 32, Architecture = "armeabi-v7a", SupportedArchitectures = new[] { "armeabi-v7a", "x86_64", "x86" }, InstalledApplications = new[] { "net.dot.G", "net.dot.H" } }, }; } private ProcessExecutionResults CallFakeProcessManager(string process, string[] arguments, TimeSpan timeout) { if (Debugger.IsAttached) { Debug.WriteLine($"Fake ADB Process Manager invoked with args: '{process} {StringUtils.FormatArguments(arguments)}' (timeout = {timeout.TotalSeconds})"); } bool timedOut = false; int exitCode = 0; string stdOut = ""; string stdErr = ""; int argStart = 0; if (arguments[0] == "-s") { s_currentDeviceSerial = arguments[1]; argStart = 2; } switch (arguments[argStart].ToLowerInvariant()) { case "get-state": stdOut = "device"; exitCode = 0; break; case "devices": s_devicesCallCount++; var s = new StringBuilder(); s.AppendLine("List of devices attached"); if (s_devicesCallCount > s_devicesReturnEmptyForNFirstCalls) { int transportId = 1; foreach (var device in _fakeDeviceList) { string state = device == _fakeDeviceList.Last() ? "offline" : "online"; s.AppendLine($"{device.DeviceSerial} {state} transportid:{transportId++}"); } } stdOut = s.ToString(); break; case "shell": if ($"{arguments[argStart + 1]} {arguments[argStart + 2]}".Equals("getprop ro.product.cpu.abilist")) { stdOut = string.Join(",", _fakeDeviceList.Single(d => d.DeviceSerial == s_currentDeviceSerial).SupportedArchitectures); } if ($"{arguments[argStart + 1]} {arguments[argStart + 2]}".Equals("getprop ro.product.cpu.abi")) { stdOut = _fakeDeviceList.Single(d => d.DeviceSerial == s_currentDeviceSerial).Architecture; } if ($"{arguments[argStart + 1]} {arguments[argStart + 2]}".Equals("getprop ro.build.version.sdk")) { stdOut = _fakeDeviceList.Single(d => d.DeviceSerial == s_currentDeviceSerial).ApiVersion + Environment.NewLine; } if ($"{arguments[argStart + 1]} {arguments[argStart + 2]}".Equals("getprop sys.boot_completed")) { // Newline is strange, but this is actually what it looks like if (s_bootCompleteCheckTimes > 0) { // Tell it we've booted. stdOut = $"1{Environment.NewLine}"; } else { stdOut = Environment.NewLine; } s_bootCompleteCheckTimes++; } if (string.Join(" ", arguments.Skip(argStart).Take(5)).Equals("shell pm list packages -3")) { stdOut = "package:" + string.Join("\npackage:", _fakeDeviceList.Single(d => d.DeviceSerial == s_currentDeviceSerial).InstalledApplications); } exitCode = 0; break; case "logcat": if (arguments[argStart + 1].Equals("-d")) { stdOut = "Sample LogCat Output"; } break; case "bugreport": var outputPath = arguments[argStart + 1]; File.WriteAllText(outputPath, "Sample BugReport Output"); break; case "install": case "reboot": case "uninstall": case "wait-for-device": case "start-server": case "kill-server": case "emu": break; default: throw new InvalidOperationException($"Fake ADB doesn't know how to handle argument: {string.Join(" ", arguments)}"); } return new ProcessExecutionResults { TimedOut = timedOut, ExitCode = exitCode, StandardError = stdErr, StandardOutput = stdOut }; } private void VerifyAdbCall(params string[] arguments) => VerifyAdbCall(Times.Once(), arguments); private void VerifyAdbCall(Times occurence, params string[] arguments) { _processManager.Verify( x => x.Run(s_adbPath, It.Is>(args => Enumerable.SequenceEqual(arguments, args)), It.IsAny()), occurence); } #endregion } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Android.Tests/InstrumentationRunnerSummaryTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable enable using System; using System.Collections.Generic; using System.Text.Json; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Android.Tests; public class InstrumentationRunnerSummaryTests { private readonly Mock _mockLogger; private readonly List _loggedMessages; public InstrumentationRunnerSummaryTests() { _mockLogger = new Mock(); _loggedMessages = new List(); _mockLogger .Setup(l => l.Log( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) .Callback((LogLevel level, EventId eventId, object state, Exception? ex, Delegate formatter) => { _loggedMessages.Add(state?.ToString() ?? ""); }); } [Fact] public void EmitRunSummary_ContainsDelimiters() { var files = new List { new() { Name = "testResults.xml", Type = "test-results" } }; RunSummaryEmitter.EmitRunSummary(_mockLogger.Object, ExitCode.SUCCESS, "android", "device1", "API 33", "arm64", 0, files); var jsonMessage = _loggedMessages.Find(m => m.Contains("<>")); Assert.NotNull(jsonMessage); Assert.Contains("<>", jsonMessage); } [Fact] public void EmitJsonResultBlock_ContainsExitCode() { RunSummaryEmitter.EmitRunSummary(_mockLogger.Object, ExitCode.TESTS_FAILED, "android", null, null, null, 1, new List()); var json = ExtractJsonFromLogs(); Assert.Equal(1, json.GetProperty("exitCode").GetInt32()); Assert.Equal("TESTS_FAILED", json.GetProperty("exitCodeName").GetString()); } [Fact] public void EmitJsonResultBlock_ContainsPlatform() { RunSummaryEmitter.EmitRunSummary(_mockLogger.Object, ExitCode.SUCCESS, "apple", "iPhone", "iOS 18", null, null, new List()); var json = ExtractJsonFromLogs(); Assert.Equal("apple", json.GetProperty("platform").GetString()); } [Fact] public void EmitJsonResultBlock_ContainsDeviceInfo() { RunSummaryEmitter.EmitRunSummary(_mockLogger.Object, ExitCode.SUCCESS, "android", "SERIAL123", "API 33", "arm64-v8a", 0, new List()); var json = ExtractJsonFromLogs(); Assert.Equal("SERIAL123", json.GetProperty("device").GetString()); Assert.Equal("API 33", json.GetProperty("deviceOsVersion").GetString()); Assert.Equal("arm64-v8a", json.GetProperty("architecture").GetString()); } [Fact] public void EmitJsonResultBlock_ContainsFileInfo() { var files = new List { new() { Name = "testResults.xml", Type = "test-results" }, new() { Name = "logcat.log", Type = "logcat" }, }; RunSummaryEmitter.EmitRunSummary(_mockLogger.Object, ExitCode.SUCCESS, "android", null, null, null, 0, files); var json = ExtractJsonFromLogs(); var filesArray = json.GetProperty("files"); Assert.Equal(2, filesArray.GetArrayLength()); Assert.Equal("testResults.xml", filesArray[0].GetProperty("name").GetString()); } [Fact] public void EmitJsonResultBlock_IncludesHelixUrls_WhenEnvVarsSet() { var files = new List { new() { Name = "testResults.xml", Type = "test-results" }, }; var originalCorrelationId = Environment.GetEnvironmentVariable("HELIX_CORRELATION_ID"); var originalFriendlyName = Environment.GetEnvironmentVariable("HELIX_WORKITEM_FRIENDLYNAME"); Environment.SetEnvironmentVariable("HELIX_CORRELATION_ID", "test-job-id"); Environment.SetEnvironmentVariable("HELIX_WORKITEM_FRIENDLYNAME", "My.Test"); try { RunSummaryEmitter.EmitRunSummary(_mockLogger.Object, ExitCode.SUCCESS, "android", null, null, null, 0, files); var json = ExtractJsonFromLogs(); Assert.Equal("test-job-id", json.GetProperty("helixJobId").GetString()); Assert.Contains("test-job-id", json.GetProperty("helixConsoleUri").GetString()); Assert.Contains("test-job-id", json.GetProperty("helixFilesUri").GetString()); } finally { Environment.SetEnvironmentVariable("HELIX_CORRELATION_ID", originalCorrelationId); Environment.SetEnvironmentVariable("HELIX_WORKITEM_FRIENDLYNAME", originalFriendlyName); } } [Fact] public void EmitJsonResultBlock_OmitsHelixUrls_WhenEnvVarsNotSet() { var files = new List { new() { Name = "testResults.xml", Type = "test-results" }, }; Environment.SetEnvironmentVariable("HELIX_CORRELATION_ID", null); Environment.SetEnvironmentVariable("HELIX_WORKITEM_FRIENDLYNAME", null); RunSummaryEmitter.EmitRunSummary(_mockLogger.Object, ExitCode.SUCCESS, "android", null, null, null, 0, files); var json = ExtractJsonFromLogs(); Assert.False(json.TryGetProperty("helixJobId", out _)); Assert.False(json.TryGetProperty("helixFilesUri", out _)); } [Fact] public void EmitJsonResultBlock_HasVersionField() { RunSummaryEmitter.EmitRunSummary(_mockLogger.Object, ExitCode.SUCCESS, "android", null, null, null, 0, new List()); var json = ExtractJsonFromLogs(); Assert.Equal(1, json.GetProperty("version").GetInt32()); } private JsonElement ExtractJsonFromLogs() { var jsonMessage = _loggedMessages.Find(m => m.Contains("<>")); Assert.NotNull(jsonMessage); var start = jsonMessage.IndexOf("<>") + "<>".Length; var end = jsonMessage.IndexOf("<>"); var jsonStr = jsonMessage[start..end].Trim(); return JsonDocument.Parse(jsonStr).RootElement; } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Android.Tests/Microsoft.DotNet.XHarness.Android.Tests.csproj ================================================  $(NetCurrent) disable ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/AppOperations/AppInstallerTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.Common.Utilities; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.AppOperations; public class AppInstallerTests : IDisposable { private static readonly string s_appPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); private static readonly string s_appIdentifier = Guid.NewGuid().ToString(); private const string UDID = "8A450AA31EA94191AD6B02455F377CC1"; private static readonly IDevice s_mockDevice = Mock.Of(x => x.UDID == UDID && x.Name == "Test iPhone" && x.OSVersion == "13.4"); private readonly Mock _processManager; private readonly Mock _mainLog; private readonly AppBundleInformation _appBundleInformation; public AppInstallerTests() { _mainLog = new Mock(); _processManager = new Mock(); _processManager.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult() { ExitCode = 0 })); Directory.CreateDirectory(s_appPath); _appBundleInformation = new AppBundleInformation( appName: "AppName", bundleIdentifier: s_appIdentifier, appPath: s_appPath, launchAppPath: s_appPath, supports32b: false, extension: null); } public void Dispose() { Directory.Delete(s_appPath, true); GC.SuppressFinalize(this); } [Fact] public async Task InstallOnSimulatorTest() { // Act var appInstaller = new AppInstaller(_processManager.Object, _mainLog.Object); var result = await appInstaller.InstallApp(_appBundleInformation, new TestTargetOs(TestTarget.Simulator_iOS64, null), s_mockDevice); // Verify Assert.Equal(0, result.ExitCode); var expectedArgs = $"--device=:v2:udid={s_mockDevice.UDID} --installsim {StringUtils.FormatArguments(s_appPath)}"; _processManager.Verify(x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), _mainLog.Object, It.IsAny(), null, It.IsAny(), It.IsAny())); } [Fact] public async Task InstallOnDeviceTest() { // Act var appInstaller = new AppInstaller(_processManager.Object, _mainLog.Object); var result = await appInstaller.InstallApp(_appBundleInformation, new TestTargetOs(TestTarget.Device_iOS, null), s_mockDevice); // Verify Assert.Equal(0, result.ExitCode); var expectedArgs = $"--devname {s_mockDevice.UDID} --installdev {StringUtils.FormatArguments(s_appPath)}"; _processManager.Verify(x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), _mainLog.Object, It.IsAny(), null, It.IsAny(), It.IsAny())); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/AppOperations/AppRunTestBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Net; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; using Moq; namespace Microsoft.DotNet.XHarness.Apple.Tests.AppOperations; public abstract class AppRunTestBase : IDisposable { protected const string AppName = "System.Text.Json.Tests.app"; protected const string AppBundleIdentifier = "net.dot.System.Text.Json.Tests"; protected const string BundleExecutable = "System.Text.Json.Tests"; protected const string SimulatorDeviceName = "Test iPhone simulator"; protected const string DeviceName = "Test iPhone"; protected static readonly string s_outputPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); protected static readonly string s_appPath = Path.Combine(s_outputPath, AppName); protected static readonly IHardwareDevice s_mockDevice = Mock.Of(x => x.BuildVersion == "17A577" && x.DeviceClass == DeviceClass.iPhone && x.DeviceIdentifier == "8A450AA31EA94191AD6B02455F377CC1" && x.UDID == "8A450AA31EA94191AD6B02455F377CC1" && x.InterfaceType == "Usb" && x.IsUsableForDebugging == true && x.Name == DeviceName && x.ProductType == "iPhone12,1" && x.ProductVersion == "13.0"); protected readonly AppBundleInformation _appBundleInfo = new(appName: AppName, bundleIdentifier: AppBundleIdentifier, appPath: s_appPath, launchAppPath: s_appPath, supports32b: false, extension: null, bundleExecutable: BundleExecutable); protected readonly string _simulatorLogPath = Path.Combine(Path.GetTempPath(), "simulator-logs"); protected readonly ISimulatorDevice _mockSimulator; protected readonly Mock _processManager; protected readonly Mock _logs; protected readonly Mock _mainLog; protected readonly Mock _snapshotReporter; protected readonly Mock _helpers; protected readonly ICrashSnapshotReporterFactory _snapshotReporterFactory; protected readonly IFileBackedLog _appLog; protected AppRunTestBase() { _mainLog = new Mock(); _processManager = new Mock(); _processManager.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult() { ExitCode = 0 })); _snapshotReporter = new Mock(); _appLog = Mock.Of( x => x.FullPath == $"./{BundleExecutable}.log" && x.Description == LogType.ApplicationLog.ToString()); _logs = new Mock(); _logs .SetupGet(x => x.Directory) .Returns(Path.Combine(s_outputPath, "logs")); _logs .Setup(x => x.CreateFile($"{AppBundleIdentifier}-mocked_timestamp.log", It.IsAny())) .Returns($"./{AppBundleIdentifier}-mocked_timestamp.log"); _logs .Setup(x => x.Create(BundleExecutable + ".log", It.IsAny(), It.IsAny())) .Returns(_appLog); _logs .Setup(x => x.Create(AppBundleIdentifier + ".log", It.IsAny(), It.IsAny())) .Returns(_appLog); var factory2 = new Mock(); factory2.SetReturnsDefault(_snapshotReporter.Object); _snapshotReporterFactory = factory2.Object; _mockSimulator = Mock.Of(x => x.UDID == "58F21118E4D34FD69EAB7860BB9B38A0" && x.Name == SimulatorDeviceName && x.LogPath == _simulatorLogPath && x.SystemLog == Path.Combine(_simulatorLogPath, "system.log")); _helpers = new Mock(); _helpers .Setup(x => x.GetTerminalName(It.IsAny())) .Returns("tty1"); _helpers .Setup(x => x.GenerateStableGuid(It.IsAny())) .Returns(Guid.NewGuid()); _helpers .SetupGet(x => x.Timestamp) .Returns("mocked_timestamp"); _helpers .Setup(x => x.GetLocalIpAddresses()) .Returns(new[] { IPAddress.Loopback, IPAddress.IPv6Loopback }); Directory.CreateDirectory(s_outputPath); } public void Dispose() { try { Directory.Delete(s_outputPath, true); } catch { // Concurrency can cause these } finally { GC.SuppressFinalize(this); } } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/AppOperations/AppRunnerTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.AppOperations; public class AppRunnerTests : AppRunTestBase { [Fact] public async Task RunOnSimulatorTest() { var captureLog = new Mock(); captureLog.SetupGet(x => x.FullPath).Returns(_simulatorLogPath); captureLog.SetupGet(x => x.Description).Returns(LogType.SystemLog.ToString()); var captureLogFactory = new Mock(); captureLogFactory .Setup(x => x.Create( Path.Combine(_logs.Object.Directory, _mockSimulator.Name + ".log"), _mockSimulator.SystemLog, false, LogType.SystemLog)) .Returns(captureLog.Object); SetupLogList(new[] { captureLog.Object }); // Act var appRunner = new AppRunner( _processManager.Object, _snapshotReporterFactory, captureLogFactory.Object, Mock.Of(), _mainLog.Object, _logs.Object, _helpers.Object); var result = await appRunner.RunApp( _appBundleInfo, new TestTargetOs(TestTarget.Simulator_tvOS, null), _mockSimulator, null, timeout: TimeSpan.FromSeconds(30), signalAppEnd: false, waitForExit: true, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", "value1") }); // Verify Assert.True(result.Succeeded); var expectedArgs = GetExpectedSimulatorMlaunchArgs(); _processManager .Verify( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); _processManager .Verify( x => x.ExecuteXcodeCommandAsync( "simctl", It.Is>(args => args.Contains("log") && args.Contains(_mockSimulator.UDID) && args.Contains("stream") && args.Any(a => a.Contains(BundleExecutable))), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); captureLog.Verify(x => x.StartCapture(), Times.AtLeastOnce); } [Fact] public async Task RunOnSimulatorWithNullEnvVariableSkipsArgument() { var captureLog = new Mock(); captureLog.SetupGet(x => x.FullPath).Returns(_simulatorLogPath); captureLog.SetupGet(x => x.Description).Returns(LogType.SystemLog.ToString()); var captureLogFactory = new Mock(); captureLogFactory .Setup(x => x.Create( Path.Combine(_logs.Object.Directory, _mockSimulator.Name + ".log"), _mockSimulator.SystemLog, false, LogType.SystemLog)) .Returns(captureLog.Object); SetupLogList(new[] { captureLog.Object }); var appRunner = new AppRunner( _processManager.Object, _snapshotReporterFactory, captureLogFactory.Object, Mock.Of(), _mainLog.Object, _logs.Object, _helpers.Object); var result = await appRunner.RunApp( _appBundleInfo, new TestTargetOs(TestTarget.Simulator_tvOS, null), _mockSimulator, null, timeout: TimeSpan.FromSeconds(30), signalAppEnd: false, waitForExit: true, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", null) }); Assert.True(result.Succeeded); _processManager.Verify( x => x.ExecuteCommandAsync( It.Is(args => !args.AsCommandLine().Contains("-setenv=appArg1=")), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); } [Fact] public async Task RunOnDeviceTest() { var deviceSystemLog = new Mock(); deviceSystemLog.SetupGet(x => x.FullPath).Returns(AppBundleIdentifier + "system.log"); deviceSystemLog.SetupGet(x => x.Description).Returns(LogType.SystemLog.ToString()); SetupLogList(new[] { deviceSystemLog.Object }); _logs .Setup(x => x.Create("device-" + DeviceName + "-mocked_timestamp.log", LogType.SystemLog.ToString(), It.IsAny())) .Returns(deviceSystemLog.Object); var deviceLogCapturer = new Mock(); var deviceLogCapturerFactory = new Mock(); deviceLogCapturerFactory .Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID)) .Returns(deviceLogCapturer.Object); var x = _logs.Object.First(); // Act var appRunner = new AppRunner( _processManager.Object, _snapshotReporterFactory, Mock.Of(), deviceLogCapturerFactory.Object, _mainLog.Object, _logs.Object, _helpers.Object); var result = await appRunner.RunApp( _appBundleInfo, new TestTargetOs(TestTarget.Device_iOS, null), s_mockDevice, null, timeout: TimeSpan.FromSeconds(30), signalAppEnd: false, waitForExit: true, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", "value1") }); // Verify Assert.True(result.Succeeded); var expectedArgs = GetExpectedDeviceMlaunchArgs(); _processManager .Verify( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); _snapshotReporter.Verify(x => x.StartCaptureAsync(), Times.AtLeastOnce); deviceSystemLog.Verify(x => x.Dispose(), Times.AtLeastOnce); } [Fact] public async Task RunOnDeviceWithAppEndSignalTest() { var deviceSystemLog = new Mock(); deviceSystemLog.SetupGet(x => x.FullPath).Returns(AppBundleIdentifier + "system.log"); deviceSystemLog.SetupGet(x => x.Description).Returns(LogType.SystemLog.ToString()); SetupLogList(new[] { deviceSystemLog.Object }); _logs .Setup(x => x.Create("device-" + DeviceName + "-mocked_timestamp.log", LogType.SystemLog.ToString(), It.IsAny())) .Returns(deviceSystemLog.Object); var deviceLogCapturer = new Mock(); var deviceLogCapturerFactory = new Mock(); deviceLogCapturerFactory .Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID)) .Returns(deviceLogCapturer.Object); var testEndSignal = Guid.NewGuid(); _helpers .Setup(x => x.GenerateGuid()) .Returns(testEndSignal); List mlaunchArguments = new(); List appOutputLogs = new(); List cancellationTokens = new(); // Endlessly running mlaunch until it gets cancelled by the signal var mlaunchCompleted = new TaskCompletionSource(); var appStarted = new TaskCompletionSource(); _processManager .Setup(x => x.ExecuteCommandAsync( Capture.In(mlaunchArguments), It.IsAny(), Capture.In(appOutputLogs), Capture.In(appOutputLogs), It.IsAny(), It.IsAny?>(), It.IsAny(), Capture.In(cancellationTokens))) .Callback(() => { // Signal we have started mlaunch appStarted.SetResult(); // When mlaunch gets signalled to shut down, shut down even our fake mlaunch cancellationTokens.Last().Register(() => mlaunchCompleted.SetResult(new ProcessExecutionResult { TimedOut = true, })); }) .Returns(mlaunchCompleted.Task); // Act var appRunner = new AppRunner( _processManager.Object, _snapshotReporterFactory, Mock.Of(), deviceLogCapturerFactory.Object, _mainLog.Object, _logs.Object, _helpers.Object); var runTask = appRunner.RunApp( _appBundleInfo, new TestTargetOs(TestTarget.Device_iOS, null), s_mockDevice, null, timeout: TimeSpan.FromSeconds(30), signalAppEnd: true, waitForExit: true, Array.Empty(), Array.Empty<(string, string?)>()); // Everything should hang now since we mimicked mlaunch not being able to tell the app quits // We will wait for XHarness to kick off the mlaunch (the app) Assert.False(runTask.IsCompleted); await Task.WhenAny(appStarted.Task, Task.Delay(1000)); // XHarness should still be running Assert.False(runTask.IsCompleted); // mlaunch should be started Assert.True(appStarted.Task.IsCompleted); // We will mimick the app writing the end signal var appLog = appOutputLogs.First(); appLog.WriteLine(testEndSignal.ToString()); // AppTester should now complete fine var result = await runTask; // Verify Assert.True(result.Succeeded); var expectedArgs = $"-setenv=RUN_END_TAG={testEndSignal} " + "--disable-memory-limits " + $"--devname {s_mockDevice.DeviceIdentifier} " + $"--launchdevbundleid {AppBundleIdentifier} " + "--wait-for-exit"; Assert.Equal(mlaunchArguments.Last().AsCommandLine(), expectedArgs); _snapshotReporter.Verify(x => x.StartCaptureAsync(), Times.AtLeastOnce); deviceSystemLog.Verify(x => x.Dispose(), Times.AtLeastOnce); } [Fact] public async Task RunOnMacCatalystTest() { var captureLog = new Mock(); captureLog.SetupGet(x => x.FullPath).Returns(_simulatorLogPath); captureLog.SetupGet(x => x.Description).Returns(LogType.SystemLog.ToString()); var captureLogFactory = new Mock(); captureLogFactory .Setup(x => x.Create( It.IsAny(), It.IsAny(), false, LogType.SystemLog)) .Returns(captureLog.Object); SetupLogList(new[] { captureLog.Object }); // Act var appRunner = new AppRunner( _processManager.Object, _snapshotReporterFactory, captureLogFactory.Object, Mock.Of(), _mainLog.Object, _logs.Object, _helpers.Object); var result = await appRunner.RunMacCatalystApp( _appBundleInfo, timeout: TimeSpan.FromSeconds(30), signalAppEnd: false, waitForExit: true, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", "value1") }); // Verify Assert.True(result.Succeeded); var expectedArgs = GetExpectedSimulatorMlaunchArgs(); _processManager .Verify( x => x.ExecuteCommandAsync( "open", It.Is>(args => args[0] == "-n" && args[1] == "-W" && args[2] == s_appPath), _mainLog.Object, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); _processManager .Verify( x => x.ExecuteCommandAsync( "log", It.Is>(args => args.Contains("stream") && args.Any(a => a.Contains(BundleExecutable))), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny?>(), It.IsAny()), Times.Once); captureLog.Verify(x => x.StartCapture(), Times.AtLeastOnce); } [Fact] public async Task RunOnDeviceNoWaitTest() { var deviceSystemLog = new Mock(); deviceSystemLog.SetupGet(x => x.FullPath).Returns(AppBundleIdentifier + "system.log"); deviceSystemLog.SetupGet(x => x.Description).Returns(LogType.SystemLog.ToString()); SetupLogList(new[] { deviceSystemLog.Object }); _logs .Setup(x => x.Create("device-" + DeviceName + "-mocked_timestamp.log", LogType.SystemLog.ToString(), It.IsAny())) .Returns(deviceSystemLog.Object); var deviceLogCapturer = new Mock(); var deviceLogCapturerFactory = new Mock(); deviceLogCapturerFactory .Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID)) .Returns(deviceLogCapturer.Object); var x = _logs.Object.First(); // Act var appRunner = new AppRunner( _processManager.Object, _snapshotReporterFactory, Mock.Of(), deviceLogCapturerFactory.Object, _mainLog.Object, _logs.Object, _helpers.Object); var result = await appRunner.RunApp( _appBundleInfo, new TestTargetOs(TestTarget.Device_iOS, null), s_mockDevice, null, timeout: TimeSpan.FromSeconds(30), signalAppEnd: false, waitForExit: false, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", "value1") }); // Verify Assert.True(result.Succeeded); var expectedArgs = GetExpectedDeviceMlaunchArgs(); _processManager .Verify( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs.Replace(" --wait-for-exit", null)), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); _snapshotReporter.Verify(x => x.StartCaptureAsync(), Times.AtLeastOnce); deviceSystemLog.Verify(x => x.Dispose(), Times.AtLeastOnce); } [Fact] public async Task RunOnSimulatorNoWaitTest() { var captureLog = new Mock(); captureLog.SetupGet(x => x.FullPath).Returns(_simulatorLogPath); captureLog.SetupGet(x => x.Description).Returns(LogType.SystemLog.ToString()); var captureLogFactory = new Mock(); captureLogFactory .Setup(x => x.Create( Path.Combine(_logs.Object.Directory, _mockSimulator.Name + ".log"), _mockSimulator.SystemLog, false, LogType.SystemLog)) .Returns(captureLog.Object); SetupLogList(new[] { captureLog.Object }); var expectedArgs = GetExpectedSimulatorMlaunchArgs(); var appLaunchedTask = new TaskCompletionSource(); ILog? appLog = null; _processManager .Setup( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Callback((MlaunchArguments args, ILog log, ILog stdoutLog, ILog stderrLog, TimeSpan timeout, Dictionary env, int verbosity, CancellationToken? ct) => { appLog = log; appLaunchedTask.SetResult(); }) .Returns(new TaskCompletionSource().Task); // This task must never complete (it represents running app) // Act var appRunner = new AppRunner( _processManager.Object, _snapshotReporterFactory, captureLogFactory.Object, Mock.Of(), _mainLog.Object, _logs.Object, _helpers.Object); var runTask = appRunner.RunApp( _appBundleInfo, new TestTargetOs(TestTarget.Simulator_tvOS, null), _mockSimulator, null, timeout: TimeSpan.FromSeconds(30), signalAppEnd: false, waitForExit: false, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", "value1") }); // No we wait for the launch of the app (which will then hang and the ScanLog will start waiting for the launch signal) await appLaunchedTask.Task; Assert.False(runTask.IsCompleted); // Now we send the signal that the app has launched Assert.NotNull(appLog); appLog!.WriteLine($"Some message"); appLog!.WriteLine($"Xamarin.Hosting: Launched {AppBundleIdentifier} with pid 39402"); appLog!.WriteLine($"Some other message"); // We should now be able to return from here since the ScanLog will finish var result = await runTask; // Verify Assert.True(result.Succeeded); _processManager .Verify( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); _processManager .Verify( x => x.ExecuteXcodeCommandAsync( "simctl", It.Is>(args => args.Contains("log") && args.Contains(_mockSimulator.UDID) && args.Contains("stream") && args.Any(a => a.Contains(BundleExecutable))), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); captureLog.Verify(x => x.StartCapture(), Times.AtLeastOnce); } [Fact] public async Task RunOnSimulatorNoWaitNoLaunchSignalTest() { var captureLog = new Mock(); captureLog.SetupGet(x => x.FullPath).Returns(_simulatorLogPath); captureLog.SetupGet(x => x.Description).Returns(LogType.SystemLog.ToString()); var captureLogFactory = new Mock(); captureLogFactory .Setup(x => x.Create( Path.Combine(_logs.Object.Directory, _mockSimulator.Name + ".log"), _mockSimulator.SystemLog, false, LogType.SystemLog)) .Returns(captureLog.Object); SetupLogList(new[] { captureLog.Object }); var expectedArgs = GetExpectedSimulatorMlaunchArgs(); var appLaunchedTask = new TaskCompletionSource(); var appRunTask = new TaskCompletionSource(); _processManager .Setup( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Callback((MlaunchArguments args, ILog log, ILog stdoutLog, ILog stderrLog, TimeSpan timeout, Dictionary env, int verbosity, CancellationToken? ct) => { appLaunchedTask.SetResult(); }) .Returns(appRunTask.Task); // This task will complete and the ScanLog task won't (we won't log the "app launched" message) // Act var appRunner = new AppRunner( _processManager.Object, _snapshotReporterFactory, captureLogFactory.Object, Mock.Of(), _mainLog.Object, _logs.Object, _helpers.Object); var runTask = appRunner.RunApp( _appBundleInfo, new TestTargetOs(TestTarget.Simulator_tvOS, null), _mockSimulator, null, timeout: TimeSpan.FromSeconds(30), signalAppEnd: false, waitForExit: false, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", "value1") }); // No we wait for the code to start launching the app await appLaunchedTask.Task; Assert.False(runTask.IsCompleted); // In this phase, the code waits for both ScanLog or the app run task // We will simulate a case when the app never reports back (never launches) appRunTask.SetResult(new ProcessExecutionResult { ExitCode = 137, // This is what we get when app run times out and is killed by our timeout TimedOut = true, }); var result = await runTask; // Verify Assert.False(result.Succeeded); Assert.True(result.TimedOut); _processManager .Verify( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); _processManager .Verify( x => x.ExecuteXcodeCommandAsync( "simctl", It.Is>(args => args.Contains("log") && args.Contains(_mockSimulator.UDID) && args.Contains("stream") && args.Any(a => a.Contains(BundleExecutable))), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); captureLog.Verify(x => x.StartCapture(), Times.AtLeastOnce); } [Fact] public async Task RunOnMacCatalystNoWaitTest() { var captureLog = new Mock(); captureLog.SetupGet(x => x.FullPath).Returns(_simulatorLogPath); captureLog.SetupGet(x => x.Description).Returns(LogType.SystemLog.ToString()); var captureLogFactory = new Mock(); captureLogFactory .Setup(x => x.Create( It.IsAny(), It.IsAny(), false, LogType.SystemLog)) .Returns(captureLog.Object); SetupLogList(new[] { captureLog.Object }); // Act var appRunner = new AppRunner( _processManager.Object, _snapshotReporterFactory, captureLogFactory.Object, Mock.Of(), _mainLog.Object, _logs.Object, _helpers.Object); var result = await appRunner.RunMacCatalystApp( _appBundleInfo, timeout: TimeSpan.FromSeconds(30), signalAppEnd: false, waitForExit: false, extraAppArguments: Array.Empty(), extraEnvVariables: Array.Empty<(string, string?)>()); // Verify Assert.True(result.Succeeded); var expectedArgs = GetExpectedSimulatorMlaunchArgs(); _processManager .Verify( x => x.ExecuteCommandAsync( "open", It.Is>(args => args.Count == 2 && args[0] == "-n" && args[1] == s_appPath), _mainLog.Object, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); captureLog.Verify(x => x.StartCapture(), Times.AtLeastOnce); } private static string GetExpectedDeviceMlaunchArgs() => "-argument=--foo=bar " + "-argument=--xyz " + "-setenv=appArg1=value1 " + "--disable-memory-limits " + $"--devname {s_mockDevice.DeviceIdentifier} " + $"--launchdevbundleid {AppBundleIdentifier} " + "--wait-for-exit"; private string GetExpectedSimulatorMlaunchArgs() => "-argument=--foo=bar " + "-argument=--xyz " + "-setenv=appArg1=value1 " + $"--device=:v2:udid={_mockSimulator.UDID} " + $"--launchsimbundleid={AppBundleIdentifier}"; private void SetupLogList(IEnumerable logs) { _logs .Setup(x => x.GetEnumerator()) .Returns(() => logs.GetEnumerator()); _logs .Setup(m => m.Count) .Returns(() => logs.Count()); _logs .Setup(m => m[It.IsAny()]) .Returns(i => logs.ElementAt(i)); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/AppOperations/AppTesterTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Listeners; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.AppOperations; public class AppTesterTests : AppRunTestBase { private const int Port = 1020; private readonly Mock _listener; private readonly Mock _testReporter; private readonly Mock _tunnelBore; private readonly Mock _listenerFactory; private readonly ITestReporterFactory _testReporterFactory; public AppTesterTests() { _listener = new Mock(); _listener .SetupGet(x => x.ConnectedTask) .Returns(Task.FromResult(true)); _testReporter = new Mock(); _testReporter .Setup(r => r.Success) .Returns(true); _testReporter .Setup(r => r.ParseResult()) .ReturnsAsync((TestExecutingResult.Succeeded, "Tests run: 1194 Passed: 1191 Inconclusive: 0 Failed: 0 Ignored: 0")); _testReporter .Setup(x => x.CollectSimulatorResult(It.IsAny())) .Returns(Task.CompletedTask); _tunnelBore = new Mock(); _tunnelBore.Setup(t => t.Close(It.IsAny())); _listenerFactory = new Mock(); _listenerFactory.SetReturnsDefault((ListenerTransport.Tcp, _listener.Object, "listener-temp-file")); _listenerFactory.Setup(f => f.TunnelBore).Returns(_tunnelBore.Object); _listener.Setup(x => x.InitializeAndGetPort()).Returns(Port); var factory2 = new Mock(); factory2.SetReturnsDefault(_snapshotReporter.Object); var factory3 = new Mock(); factory3.SetReturnsDefault(_testReporter.Object); _testReporterFactory = factory3.Object; Directory.CreateDirectory(s_outputPath); } [Theory] [InlineData(false)] [InlineData(true)] public async Task TestOnSimulatorTest(bool useTunnel) { var testResultFilePath = Path.GetTempFileName(); var listenerLogFile = Mock.Of(x => x.FullPath == testResultFilePath); File.WriteAllLines(testResultFilePath, new[] { "Some result here", "Tests run: 124", "Some result there" }); _logs .Setup(x => x.Create("test-ios-simulator-64-mocked_timestamp.log", "TestLog", It.IsAny())) .Returns(listenerLogFile); var captureLog = new Mock(); captureLog.SetupGet(x => x.FullPath).Returns(_simulatorLogPath); var captureLogFactory = new Mock(); captureLogFactory .Setup(x => x.Create( Path.Combine(_logs.Object.Directory, _mockSimulator.Name + ".log"), _mockSimulator.SystemLog, false, It.IsAny())) .Returns(captureLog.Object); _listenerFactory.Setup(f => f.UseTunnel).Returns(useTunnel); // Act var appTester = new AppTester( _processManager.Object, _listenerFactory.Object, _snapshotReporterFactory, captureLogFactory.Object, Mock.Of(), _testReporterFactory, new XmlResultParser(), _mainLog.Object, _logs.Object, _helpers.Object); var (result, resultMessage) = await appTester.TestApp( _appBundleInfo, new TestTargetOs(TestTarget.Simulator_tvOS, null), _mockSimulator, null, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30), signalAppEnd: false, extraAppArguments: new string[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", "value1") }); // Verify Assert.Equal(TestExecutingResult.Succeeded, result); Assert.Equal("Tests run: 1194 Passed: 1191 Inconclusive: 0 Failed: 0 Ignored: 0", resultMessage); var expectedArgs = GetExpectedSimulatorMlaunchArgs(); _processManager .Verify( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), _mainLog.Object, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); _processManager .Verify( x => x.ExecuteXcodeCommandAsync( "simctl", It.Is>(args => args.Contains("log") && args.Contains(_mockSimulator.UDID) && args.Contains("stream") && args.Any(a => a.Contains(BundleExecutable))), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); _listener.Verify(x => x.InitializeAndGetPort(), Times.AtLeastOnce); _listener.Verify(x => x.StartAsync(), Times.AtLeastOnce); _listener.Verify(x => x.Dispose(), Times.AtLeastOnce); captureLog.Verify(x => x.StartCapture(), Times.AtLeastOnce); } [Fact] public async Task TestOnSimulatorWithNullEnvVariableSkipsArgument() { var testResultFilePath = Path.GetTempFileName(); var listenerLogFile = Mock.Of(x => x.FullPath == testResultFilePath); File.WriteAllLines(testResultFilePath, new[] { "Some result here", "Tests run: 124", "Some result there" }); _logs .Setup(x => x.Create("test-ios-simulator-64-mocked_timestamp.log", "TestLog", It.IsAny())) .Returns(listenerLogFile); var captureLog = new Mock(); captureLog.SetupGet(x => x.FullPath).Returns(_simulatorLogPath); var captureLogFactory = new Mock(); captureLogFactory .Setup(x => x.Create( Path.Combine(_logs.Object.Directory, _mockSimulator.Name + ".log"), _mockSimulator.SystemLog, false, It.IsAny())) .Returns(captureLog.Object); var appTester = new AppTester( _processManager.Object, _listenerFactory.Object, _snapshotReporterFactory, captureLogFactory.Object, Mock.Of(), _testReporterFactory, new XmlResultParser(), _mainLog.Object, _logs.Object, _helpers.Object); var (result, resultMessage) = await appTester.TestApp( _appBundleInfo, new TestTargetOs(TestTarget.Simulator_tvOS, null), _mockSimulator, null, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30), signalAppEnd: false, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", null) }); Assert.Equal(TestExecutingResult.Succeeded, result); Assert.Equal("Tests run: 1194 Passed: 1191 Inconclusive: 0 Failed: 0 Ignored: 0", resultMessage); _processManager.Verify( x => x.ExecuteCommandAsync( It.Is(args => !args.AsCommandLine().Contains("-setenv=appArg1=")), _mainLog.Object, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); } [Theory] [InlineData(false)] [InlineData(true)] public async Task TestOnDeviceTest(bool useTunnel) { var deviceSystemLog = new Mock(); deviceSystemLog.SetupGet(x => x.FullPath).Returns(Path.GetTempFileName()); var deviceLogCapturer = new Mock(); var deviceLogCapturerFactory = new Mock(); deviceLogCapturerFactory .Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID)) .Returns(deviceLogCapturer.Object); var testResultFilePath = Path.GetTempFileName(); var listenerLogFile = Mock.Of(x => x.FullPath == testResultFilePath); File.WriteAllLines(testResultFilePath, new[] { "Some result here", "Tests run: 124", "Some result there" }); _logs .Setup(x => x.Create("test-ios-device-mocked_timestamp.log", "TestLog", It.IsAny())) .Returns(listenerLogFile); _logs .Setup(x => x.Create($"device-{DeviceName}-mocked_timestamp.log", LogType.SystemLog.ToString(), It.IsAny())) .Returns(deviceSystemLog.Object); // set tunnel bore expectation if (useTunnel) { _tunnelBore.Setup(t => t.Create(DeviceName, It.IsAny())); } _listenerFactory .Setup(f => f.UseTunnel) .Returns(useTunnel); // Act var appTester = new AppTester( _processManager.Object, _listenerFactory.Object, _snapshotReporterFactory, Mock.Of(), deviceLogCapturerFactory.Object, _testReporterFactory, new XmlResultParser(), _mainLog.Object, _logs.Object, _helpers.Object); var (result, resultMessage) = await appTester.TestApp( _appBundleInfo, new TestTargetOs(TestTarget.Device_iOS, null), s_mockDevice, null, timeout: TimeSpan.FromSeconds(30), testLaunchTimeout: TimeSpan.FromSeconds(30), signalAppEnd: false, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", "value1") }); // Verify Assert.Equal(TestExecutingResult.Succeeded, result); Assert.Equal("Tests run: 1194 Passed: 1191 Inconclusive: 0 Failed: 0 Ignored: 0", resultMessage); var expectedArgs = GetExpectedDeviceMlaunchArgs( useTunnel: useTunnel, extraArgs: "-setenv=appArg1=value1 -argument=--foo=bar -argument=--xyz "); _processManager .Verify( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.Is>(d => d["appArg1"] == "value1"), It.IsAny(), It.IsAny()), Times.Once); _listener.Verify(x => x.InitializeAndGetPort(), Times.AtLeastOnce); _listener.Verify(x => x.StartAsync(), Times.AtLeastOnce); _listener.Verify(x => x.Dispose(), Times.AtLeastOnce); // verify that we do close the tunnel when it was used // we dont want to leak a process if (useTunnel) { _tunnelBore.Verify(t => t.Close(s_mockDevice.DeviceIdentifier)); } _snapshotReporter.Verify(x => x.StartCaptureAsync(), Times.AtLeastOnce); deviceSystemLog.Verify(x => x.Dispose(), Times.AtLeastOnce); } [Theory] [InlineData("MyClass.MyMethod")] [InlineData("MyClass.MyMethod", "MyClass.MySecondMethod")] public async Task TestOnDeviceWithSkippedTestsTest(params string[] skippedTests) { var deviceSystemLog = new Mock(); deviceSystemLog.SetupGet(x => x.FullPath).Returns(Path.GetTempFileName()); var deviceLogCapturer = new Mock(); var deviceLogCapturerFactory = new Mock(); deviceLogCapturerFactory .Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID)) .Returns(deviceLogCapturer.Object); var testResultFilePath = Path.GetTempFileName(); var listenerLogFile = Mock.Of(x => x.FullPath == testResultFilePath); File.WriteAllLines(testResultFilePath, new[] { "Some result here", "Tests run: 124", "Some result there" }); _logs .Setup(x => x.Create("test-ios-device-mocked_timestamp.log", "TestLog", It.IsAny())) .Returns(listenerLogFile); _logs .Setup(x => x.Create($"device-{DeviceName}-mocked_timestamp.log", LogType.SystemLog.ToString(), It.IsAny())) .Returns(deviceSystemLog.Object); // Act var appTester = new AppTester( _processManager.Object, _listenerFactory.Object, _snapshotReporterFactory, Mock.Of(), deviceLogCapturerFactory.Object, _testReporterFactory, new XmlResultParser(), _mainLog.Object, _logs.Object, _helpers.Object); var (result, resultMessage) = await appTester.TestApp( _appBundleInfo, new TestTargetOs(TestTarget.Device_iOS, null), s_mockDevice, null, timeout: TimeSpan.FromSeconds(30), testLaunchTimeout: TimeSpan.FromSeconds(30), signalAppEnd: false, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", "value1") }, skippedMethods: skippedTests); // Verify Assert.Equal(TestExecutingResult.Succeeded, result); Assert.Equal("Tests run: 1194 Passed: 1191 Inconclusive: 0 Failed: 0 Ignored: 0", resultMessage); var skippedTestsArg = $"-setenv=NUNIT_RUN_ALL=false -setenv=NUNIT_SKIPPED_METHODS={string.Join(',', skippedTests)} "; var expectedArgs = GetExpectedDeviceMlaunchArgs(skippedTestsArg, extraArgs: "-setenv=appArg1=value1 -argument=--foo=bar -argument=--xyz "); _processManager .Verify( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); _listener.Verify(x => x.InitializeAndGetPort(), Times.AtLeastOnce); _listener.Verify(x => x.StartAsync(), Times.AtLeastOnce); _listener.Verify(x => x.Dispose(), Times.AtLeastOnce); _snapshotReporter.Verify(x => x.StartCaptureAsync(), Times.AtLeastOnce); deviceSystemLog.Verify(x => x.Dispose(), Times.AtLeastOnce); } [Theory] [InlineData("MyClass")] [InlineData("MyClass", "MySecondClass")] public async Task TestOnDeviceWithSkippedClassesTestTest(params string[] skippedClasses) { var deviceSystemLog = new Mock(); deviceSystemLog.SetupGet(x => x.FullPath).Returns(Path.GetTempFileName()); var deviceLogCapturer = new Mock(); var deviceLogCapturerFactory = new Mock(); deviceLogCapturerFactory .Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID)) .Returns(deviceLogCapturer.Object); var testResultFilePath = Path.GetTempFileName(); var listenerLogFile = Mock.Of(x => x.FullPath == testResultFilePath); File.WriteAllLines(testResultFilePath, new[] { "Some result here", "Tests run: 124", "Some result there" }); _logs .Setup(x => x.Create("test-ios-device-mocked_timestamp.log", "TestLog", It.IsAny())) .Returns(listenerLogFile); _logs .Setup(x => x.Create($"device-{DeviceName}-mocked_timestamp.log", LogType.SystemLog.ToString(), It.IsAny())) .Returns(deviceSystemLog.Object); // Act var appTester = new AppTester(_processManager.Object, _listenerFactory.Object, _snapshotReporterFactory, Mock.Of(), deviceLogCapturerFactory.Object, _testReporterFactory, new XmlResultParser(), _mainLog.Object, _logs.Object, _helpers.Object); var (result, resultMessage) = await appTester.TestApp( _appBundleInfo, new TestTargetOs(TestTarget.Device_iOS, null), s_mockDevice, null, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", "value1") }, timeout: TimeSpan.FromSeconds(30), testLaunchTimeout: TimeSpan.FromSeconds(30), signalAppEnd: false, skippedTestClasses: skippedClasses); // Verify Assert.Equal(TestExecutingResult.Succeeded, result); Assert.Equal("Tests run: 1194 Passed: 1191 Inconclusive: 0 Failed: 0 Ignored: 0", resultMessage); var skippedTestsArg = $"-setenv=NUNIT_RUN_ALL=false -setenv=NUNIT_SKIPPED_CLASSES={string.Join(',', skippedClasses)} "; var expectedArgs = GetExpectedDeviceMlaunchArgs(skippedTestsArg, extraArgs: "-setenv=appArg1=value1 -argument=--foo=bar -argument=--xyz "); _processManager .Verify( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); _listener.Verify(x => x.InitializeAndGetPort(), Times.AtLeastOnce); _listener.Verify(x => x.StartAsync(), Times.AtLeastOnce); _listener.Verify(x => x.Dispose(), Times.AtLeastOnce); _snapshotReporter.Verify(x => x.StartCaptureAsync(), Times.AtLeastOnce); deviceSystemLog.Verify(x => x.Dispose(), Times.AtLeastOnce); } [Fact] public async Task TestOnMacCatalystTest() { var testResultFilePath = Path.GetTempFileName(); var listenerLogFile = Mock.Of(x => x.FullPath == testResultFilePath); File.WriteAllLines(testResultFilePath, new[] { "Some result here", "Tests run: 124", "Some result there" }); _logs .Setup(x => x.Create("test-maccatalyst-mocked_timestamp.log", "TestLog", It.IsAny())) .Returns(listenerLogFile); var captureLog = new Mock(); captureLog.SetupGet(x => x.FullPath).Returns(_simulatorLogPath); var captureLogFactory = new Mock(); captureLogFactory .Setup(x => x.Create( It.IsAny(), "/var/log/system.log", false, It.IsAny())) .Returns(captureLog.Object); // Act var appTester = new AppTester( _processManager.Object, _listenerFactory.Object, _snapshotReporterFactory, captureLogFactory.Object, Mock.Of(), _testReporterFactory, new XmlResultParser(), _mainLog.Object, _logs.Object, _helpers.Object); var (result, resultMessage) = await appTester.TestMacCatalystApp( _appBundleInfo, timeout: TimeSpan.FromSeconds(30), testLaunchTimeout: TimeSpan.FromSeconds(30), signalAppEnd: false, extraAppArguments: new[] { "--foo=bar", "--xyz" }, extraEnvVariables: new (string, string?)[] { ("appArg1", "value1") }); // Verify Assert.Equal(TestExecutingResult.Succeeded, result); Assert.Equal("Tests run: 1194 Passed: 1191 Inconclusive: 0 Failed: 0 Ignored: 0", resultMessage); _processManager .Verify( x => x.ExecuteCommandAsync( "open", It.Is>(args => args.Contains(s_appPath) && args.Contains("--foo=bar") && args.Contains("--foo=bar")), _mainLog.Object, It.IsAny(), It.IsAny(), It.IsAny(), It.Is>(envVars => envVars["NUNIT_HOSTNAME"] == "127.0.0.1" && envVars["NUNIT_HOSTPORT"] == Port.ToString() && envVars["NUNIT_AUTOEXIT"] == "true" && envVars["NUNIT_XML_VERSION"] == "xUnit" && envVars["NUNIT_ENABLE_XML_OUTPUT"] == "true"), It.IsAny()), Times.Once); _listener.Verify(x => x.InitializeAndGetPort(), Times.AtLeastOnce); _listener.Verify(x => x.StartAsync(), Times.AtLeastOnce); _listener.Verify(x => x.Dispose(), Times.AtLeastOnce); } [Fact] public async Task TestOnDeviceWithAppEndSignalTest() { var deviceSystemLog = new Mock(); deviceSystemLog.SetupGet(x => x.FullPath).Returns(Path.GetTempFileName()); var deviceLogCapturer = new Mock(); var deviceLogCapturerFactory = new Mock(); deviceLogCapturerFactory .Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID)) .Returns(deviceLogCapturer.Object); var testResultFilePath = Path.GetTempFileName(); var listenerLogFile = Mock.Of(x => x.FullPath == testResultFilePath); File.WriteAllLines(testResultFilePath, new[] { "Some result here", "Tests run: 124", "Some result there" }); _logs .Setup(x => x.Create("test-ios-device-mocked_timestamp.log", "TestLog", It.IsAny())) .Returns(listenerLogFile); _logs .Setup(x => x.Create($"device-{DeviceName}-mocked_timestamp.log", LogType.SystemLog.ToString(), It.IsAny())) .Returns(deviceSystemLog.Object); _tunnelBore.Setup(t => t.Create(DeviceName, It.IsAny())); _listenerFactory .Setup(f => f.UseTunnel) .Returns(true); var testEndSignal = Guid.NewGuid(); _helpers .Setup(x => x.GenerateGuid()) .Returns(testEndSignal); List mlaunchArguments = new(); List appOutputLogs = new(); List cancellationTokens = new(); // Endlessly running mlaunch until it gets cancelled by the signal var mlaunchCompleted = new TaskCompletionSource(); var appStarted = new TaskCompletionSource(); _processManager .Setup(x => x.ExecuteCommandAsync( Capture.In(mlaunchArguments), It.IsAny(), Capture.In(appOutputLogs), Capture.In(appOutputLogs), It.IsAny(), It.IsAny?>(), It.IsAny(), Capture.In(cancellationTokens))) .Callback(() => { // Signal we have started mlaunch appStarted.SetResult(); // When mlaunch gets signalled to shut down, shut down even our fake mlaunch cancellationTokens.Last().Register(() => mlaunchCompleted.SetResult(new ProcessExecutionResult { TimedOut = true, })); }) .Returns(mlaunchCompleted.Task); // Act var appTester = new AppTester( _processManager.Object, _listenerFactory.Object, _snapshotReporterFactory, Mock.Of(), deviceLogCapturerFactory.Object, _testReporterFactory, new XmlResultParser(), _mainLog.Object, _logs.Object, _helpers.Object); var testTask = appTester.TestApp( _appBundleInfo, new TestTargetOs(TestTarget.Device_iOS, null), s_mockDevice, null, timeout: TimeSpan.FromMinutes(30), testLaunchTimeout: TimeSpan.FromMinutes(30), signalAppEnd: true, Array.Empty(), Array.Empty<(string, string?)>()); // Everything should hang now since we mimicked mlaunch not being able to tell the app quits // We will wait for XHarness to kick off the mlaunch (the app) Assert.False(testTask.IsCompleted); await Task.WhenAny(appStarted.Task, Task.Delay(1000)); // XHarness should still be running Assert.False(testTask.IsCompleted); // mlaunch should be started Assert.True(appStarted.Task.IsCompleted); // We will mimick the app writing the end signal var appLog = appOutputLogs.First(); appLog.WriteLine(testEndSignal.ToString()); // AppTester should now complete fine but we safe guard it to be sure await Task.WhenAny(testTask, Task.Delay(10000)); Assert.True(testTask.IsCompleted, "Test tag wasn't detected"); var (result, resultMessage) = await testTask; // Verify Assert.Equal(TestExecutingResult.Succeeded, result); Assert.Equal("Tests run: 1194 Passed: 1191 Inconclusive: 0 Failed: 0 Ignored: 0", resultMessage); var expectedArgs = GetExpectedDeviceMlaunchArgs( useTunnel: true, extraArgs: $"-setenv=RUN_END_TAG={testEndSignal} "); Assert.Equal(mlaunchArguments.Last().AsCommandLine(), expectedArgs); _listener.Verify(x => x.InitializeAndGetPort(), Times.AtLeastOnce); _listener.Verify(x => x.StartAsync(), Times.AtLeastOnce); _listener.Verify(x => x.Dispose(), Times.AtLeastOnce); // verify that we do close the tunnel when it was used // we dont want to leak a process _tunnelBore.Verify(t => t.Close(s_mockDevice.DeviceIdentifier)); _snapshotReporter.Verify(x => x.StartCaptureAsync(), Times.AtLeastOnce); deviceSystemLog.Verify(x => x.Dispose(), Times.AtLeastOnce); } private string GetExpectedDeviceMlaunchArgs(string? skippedTests = null, bool useTunnel = false, string? extraArgs = null) => "-setenv=NUNIT_AUTOEXIT=true " + $"-setenv=NUNIT_HOSTPORT={Port} " + "-setenv=NUNIT_ENABLE_XML_OUTPUT=true " + "-setenv=NUNIT_XML_VERSION=xUnit " + skippedTests + extraArgs + "-setenv=NUNIT_HOSTNAME=127.0.0.1,::1 " + "--disable-memory-limits " + $"--devname {s_mockDevice.DeviceIdentifier} " + (useTunnel ? "-setenv=USE_TCP_TUNNEL=true " : null) + $"--launchdevbundleid {AppBundleIdentifier} " + "--wait-for-exit"; private string GetExpectedSimulatorMlaunchArgs() => "-setenv=NUNIT_AUTOEXIT=true " + $"-setenv=NUNIT_HOSTPORT={Port} " + "-setenv=NUNIT_ENABLE_XML_OUTPUT=true " + "-setenv=NUNIT_XML_VERSION=xUnit " + "-setenv=appArg1=value1 " + "-argument=--foo=bar " + "-argument=--xyz " + "-setenv=NUNIT_HOSTNAME=127.0.0.1 " + $"--device=:v2:udid={_mockSimulator.UDID} " + $"--launchsimbundleid={AppBundleIdentifier}"; } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/AppOperations/AppUninstallerTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.Common.Utilities; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.AppOperations; public class AppUninstallerTests { private const string DeviceName = "Test iPad"; private const string AppBundleId = "some.bundle.name.app"; private const string UDID = "8A450AA31EA94191AD6B02455F377CC1"; private readonly Mock _processManager; private readonly Mock _mainLog; private readonly AppUninstaller _appUninstaller; public AppUninstallerTests() { _mainLog = new Mock(); _processManager = new Mock(); _processManager.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult() { ExitCode = 0 })); _appUninstaller = new AppUninstaller(_processManager.Object, _mainLog.Object); } [Fact] public async Task UninstallFromSimulatorTest() { var simulator = Mock.Of(x => x.Name == DeviceName && x.UDID == UDID); // Act var result = await _appUninstaller.UninstallSimulatorApp(simulator, AppBundleId); // Verify Assert.Equal(0, result.ExitCode); var expectedArgs = $"uninstall {UDID} {StringUtils.FormatArguments(AppBundleId)}"; _processManager.Verify(x => x.ExecuteXcodeCommandAsync( "simctl", It.Is(args => string.Join(" ", args) == expectedArgs), _mainLog.Object, It.IsAny(), It.IsAny())); } [Fact] public async Task UninstallFromDeviceTest() { var device = Mock.Of(x => x.Name == DeviceName && x.UDID == UDID); // Act var result = await _appUninstaller.UninstallDeviceApp(device, AppBundleId); // Verify Assert.Equal(0, result.ExitCode); var expectedArgs = $"--uninstalldevbundleid {StringUtils.FormatArguments(AppBundleId)} --devname {UDID}"; _processManager.Verify(x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == expectedArgs), _mainLog.Object, It.IsAny(), null, It.IsAny(), It.IsAny())); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/DeviceFinderTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests; public class DeviceFinderTests { private readonly IDeviceFinder _deviceFinder; private readonly Mock _deviceLoader; private readonly Mock _simulatorLoader; private readonly List _hardwareDevices = new(); public DeviceFinderTests() { _deviceLoader = new Mock(); _deviceLoader .Setup(x => x.LoadDevices(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); _deviceLoader .SetupGet(x => x.ConnectedDevices) .Returns(_hardwareDevices); _deviceLoader .SetupGet(x => x.ConnectedTV) .Returns(_hardwareDevices.Where(d => d.DevicePlatform == DevicePlatform.tvOS)); _deviceLoader .SetupGet(x => x.Connected64BitIOS) .Returns(_hardwareDevices.Where(d => d.DevicePlatform == DevicePlatform.iOS && d.Architecture == Architecture.ARM64)); _simulatorLoader = new Mock(); _deviceFinder = new DeviceFinder(_deviceLoader.Object, _simulatorLoader.Object); } [Fact] public async Task CorrectTypeOfDeviceIsFoundTest() { _hardwareDevices.Add(CreateDevice(DeviceClass.iPhone, "8A450AA31EA94191AD6B02455F377CC1")); _hardwareDevices.Add(CreateDevice(DeviceClass.AppleTV, "30C0630E03EB40A19F9A40D61E66796B")); var device = await _deviceFinder.FindDevice(new TestTargetOs(TestTarget.Device_tvOS, null), null, new MemoryLog(), false); Assert.Equal("30C0630E03EB40A19F9A40D61E66796B", device.Device.UDID); _hardwareDevices.Clear(); _hardwareDevices.Add(CreateDevice(DeviceClass.iPhone, "8A450AA31EA94191AD6B02455F377CC1")); _hardwareDevices.Add(CreateDevice(DeviceClass.iPhone, "30C0630E03EB40A19F9A40D61E66796B")); await Assert.ThrowsAsync(async () => await _deviceFinder.FindDevice(new TestTargetOs(TestTarget.Device_tvOS, null), null, new MemoryLog(), false)); } [Fact] public async Task DeviceIsFoundByNameTest() { _hardwareDevices.Add(CreateDevice(DeviceClass.iPhone, "8A450AA31EA94191AD6B02455F377CC1")); _hardwareDevices.Add(CreateDevice(DeviceClass.iPhone, "30C0630E03EB40A19F9A40D61E66796B")); var device = await _deviceFinder.FindDevice(new TestTargetOs(TestTarget.Device_iOS, null), "30C0630E03EB40A19F9A40D61E66796B", new MemoryLog(), false); Assert.Equal("30C0630E03EB40A19F9A40D61E66796B", device.Device.UDID); await Assert.ThrowsAsync(async () => await _deviceFinder.FindDevice(new TestTargetOs(TestTarget.Device_iOS, null), "unknown", new MemoryLog(), false)); } [Fact] public async Task OnlyPairedDevicesAreFoundTest() { _hardwareDevices.Add(CreateDevice(DeviceClass.iPhone, "8A450AA31EA94191AD6B02455F377CC1", false)); _hardwareDevices.Add(CreateDevice(DeviceClass.iPhone, "30C0630E03EB40A19F9A40D61E66796B")); var device = await _deviceFinder.FindDevice(new TestTargetOs(TestTarget.Device_iOS, null), null, new MemoryLog(), false); Assert.Equal("30C0630E03EB40A19F9A40D61E66796B", device.Device.UDID); _hardwareDevices.Clear(); _hardwareDevices.Add(CreateDevice(DeviceClass.iPhone, "8A450AA31EA94191AD6B02455F377CC1", false)); _hardwareDevices.Add(CreateDevice(DeviceClass.iPhone, "30C0630E03EB40A19F9A40D61E66796B", false)); await Assert.ThrowsAsync(async () => await _deviceFinder.FindDevice(new TestTargetOs(TestTarget.Device_iOS, null), null, new MemoryLog(), false)); } private static IHardwareDevice CreateDevice(DeviceClass deviceClass, string udid, bool isPaired = true) => new Device( buildVersion: "17A577", deviceClass: deviceClass, deviceIdentifier: udid, interfaceType: "USB", isUsableForDebugging: true, name: "Test iPhone", productType: "iPhone12,1", productVersion: "12.1", isPaired: isPaired); } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/ErrorKnowledgeBaseTests.cs ================================================ using System; using System.IO; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests; public class ErrorKnowledgeBaseTests : IDisposable { private readonly ErrorKnowledgeBase _errorKnowledgeBase; private readonly string _logPath = Path.GetTempFileName(); public ErrorKnowledgeBaseTests() { _errorKnowledgeBase = new ErrorKnowledgeBase(); } public void Dispose() { if (File.Exists(_logPath)) { File.Delete(_logPath); } GC.SuppressFinalize(this); } [Fact] public void WrongArchPresentTest() { var expectedFailureMessage = "IncorrectArchitecture: Failed to find matching device arch for the application"; using (var log = new LogFile("test", _logPath)) { // write some data in it log.WriteLine("InstallingEmbeddedProfile: 65%"); log.WriteLine("PercentComplete: 30"); log.WriteLine("Status: InstallingEmbeddedProfile"); log.WriteLine("VerifyingApplication: 70%"); log.WriteLine("PercentComplete: 40"); log.WriteLine("Status: VerifyingApplication"); log.WriteLine( "IncorrectArchitecture: Failed to find matching arch for 64-bit Mach-O input file /private/var/installd/Library/Caches/com.apple.mobile.installd.staging/temp.Ic8Ank/extracted/monotouchtest.app/monotouchtest"); log.Flush(); Assert.True(_errorKnowledgeBase.IsKnownInstallIssue(log, out var failure)); Assert.Equal(expectedFailureMessage, failure?.HumanMessage); } } [Fact] public void WrongArchNotPresentTest() { using (var log = new LogFile("test", _logPath)) { // write some data in it log.WriteLine("InstallingEmbeddedProfile: 65%"); log.WriteLine("PercentComplete: 30"); log.WriteLine("Status: InstallingEmbeddedProfile"); log.WriteLine("VerifyingApplication: 70%"); log.WriteLine("PercentComplete: 40"); log.WriteLine("Status: VerifyingApplication"); log.Flush(); Assert.False(_errorKnowledgeBase.IsKnownInstallIssue(log, out var failure)); Assert.Null(failure); } } [Fact] public void UsbIssuesPresentTest() { var expectedFailureMessage = "Failed to communicate with the device. Please ensure the cable is properly connected, and try rebooting the device"; using (var log = new LogFile("test", _logPath)) { // initial lines are not interesting log.WriteLine("InstallingEmbeddedProfile: 65%"); log.WriteLine("PercentComplete: 30"); log.WriteLine("Status: InstallingEmbeddedProfile"); log.WriteLine("VerifyingApplication: 70%"); log.WriteLine("PercentComplete: 40"); log.WriteLine("Xamarin.Hosting.MobileDeviceException: Failed to communicate with the device. Please ensure the cable is properly connected, and try rebooting the device (error: 0xe8000065 kAMDMuxConnectError)"); log.Flush(); Assert.True(_errorKnowledgeBase.IsKnownTestIssue(log, out var failure)); Assert.Equal(expectedFailureMessage, failure?.HumanMessage); } } [Fact] public void UsbIssuesMissingTest() { using (var log = new LogFile("test", _logPath)) { // initial lines are not interesting log.WriteLine("InstallingEmbeddedProfile: 65%"); log.WriteLine("PercentComplete: 30"); log.WriteLine("Status: InstallingEmbeddedProfile"); log.WriteLine("VerifyingApplication: 70%"); log.WriteLine("PercentComplete: 40"); log.Flush(); Assert.False(_errorKnowledgeBase.IsKnownTestIssue(log, out var failure)); Assert.Null(failure); } } [Fact] public void DeviceLockedTest() { var expectedFailureMessage = "Cannot launch the application because the device is locked. Please unlock the device and try again"; using (var log = new LogFile("test", _logPath)) { log.WriteLine("05:55:56.7712200 05:55:56.7712030 Xamarin.Hosting: Mounting developer image on 'iPremek'"); log.WriteLine("05:55:56.7716040 05:55:56.7715960 Xamarin.Hosting: Mounted developer image on 'iPremek'"); log.WriteLine("05:55:56.8494160 05:55:56.8494020 error MT1031: Could not launch the app 'net.dot.HelloiOS' on the device 'iPremek' because the device is locked. Please unlock the device and try again."); log.WriteLine("05:55:56.8537390 05:55:56.8537300 at Xamarin.Launcher.DevController+<>c__DisplayClass14_0.b__0 () [0x0059d] in /Users/rolf/work/maccore/xcode12/maccore/tools/mlaunch/Xamarin.Hosting/Xamarin.Launcher/controller-device.cs:372"); log.WriteLine("05:55:56.8537720 05:55:56.8537700 at Xamarin.Launcher.DevController.LaunchDeviceBundleAsync (System.String app_path, Xamarin.Hosting.DeviceLaunchConfig config) [0x00111] in /Users/rolf/work/maccore/xcode12/maccore/tools/mlaunch/Xamarin.Hosting/Xamarin.Launcher/controller-device.cs:176"); log.WriteLine("05:55:56.8537800 05:55:56.8537790 at Xamarin.Utils.NSRunLoopExtensions.RunUntilTaskCompletion[T] (Foundation.NSRunLoop this, System.Threading.Tasks.Task`1[TResult] task) [0x00082] in /Users/rolf/work/maccore/xcode12/maccore/tools/mlaunch/Xamarin.Hosting/Xamarin.Utils/Extensions.cs:35"); log.WriteLine("05:55:56.8537870 05:55:56.8537860 at Xamarin.Launcher.Driver.Main2 (System.String[] args) [0x00b43] in /Users/rolf/work/maccore/xcode12/maccore/tools/mlaunch/Xamarin.Hosting/Xamarin.Launcher/Main.cs:458"); log.WriteLine("05:55:56.8538290 05:55:56.8538250 at Xamarin.Launcher.Driver.Main (System.String[] args) [0x0006d] in /Users/rolf/work/maccore/xcode12/maccore/tools/mlaunch/Xamarin.Hosting/Xamarin.Launcher/Main.cs:150"); log.WriteLine("05:55:56.8618920 05:55:56.8618780 Process mlaunch exited with 1"); log.WriteLine("05:56:01.8765370 05:56:01.8765240 Killing process tree of 2797..."); log.WriteLine("05:56:01.8938830 05:56:01.8938670 Pids to kill: 2797"); log.Flush(); Assert.True(_errorKnowledgeBase.IsKnownTestIssue(log, out var failure)); Assert.Equal(expectedFailureMessage, failure?.HumanMessage); } } [Fact] public void DeviceUpdateNotFinishedTest() { var expectedFailureMessage = "Cannot launch the application because the device's update hasn't been finished. The setup assistant is still running. Please finish the device OS update on the device"; using (var log = new LogFile("test", _logPath)) { log.WriteLine("[08:44:10.0123870] Xamarin.Hosting: Mounted developer image on 'DNCENGTVOS-090'"); log.WriteLine("[08:44:10.7259750] warning MT1043: Failed to launch the application using the instruments service. Will try launching the app using gdb service."); log.WriteLine("[08:44:10.7260060] "); log.WriteLine("[08:44:10.7260580] --- inner exception"); log.WriteLine("[08:44:10.7265060] error HE0003: Failed to launch the application 'net.dot.System.Buffers.Tests' on 'DNCENGTVOS-090: 1 (Request to launch net.dot.System.Buffers.Tests failed.)"); log.WriteLine("[08:44:10.7265220] "); log.WriteLine("[08:44:10.7265280] ---"); log.WriteLine("[08:44:10.7278170] Launching 'net.dot.System.Buffers.Tests' on the device 'DNCENGTVOS-090'"); log.WriteLine("[08:44:11.3152170] Launching /private/var/containers/Bundle/Application/27FA8535-C645-413A-ABE9-2ABD0BC6086B/System.Buffers.Tests.app"); log.WriteLine("[08:44:11.3156130] Xamarin.Hosting: Sending command: $A208,0,2f707269766174652f7661722f636f6e7461696e6572732f42756e646c652f4170706c69636174696f6e2f32374641383533352d433634352d343133412d414245392d3241424430424336303836422f53797374656d2e427566666572732e54657374732e617070#43"); log.WriteLine("[08:44:11.3174180] Xamarin.Hosting: Received command: OK"); log.WriteLine("[08:44:11.3174360] Xamarin.Hosting: Sending command: $qLaunchSuccess#a5"); log.WriteLine("[08:44:11.3864920] Xamarin.Hosting: Received command: EThe operation couldnt be completed. [PBD] Denying open-application request for reason: Disabled (Cannot launch app 'net.dot.System.Buffers.Tests' while Setup Assistant is running)"); log.WriteLine("[08:44:11.3886900] error MT1007: Failed to launch the application 'net.dot.System.Buffers.Tests' on the device 'DNCENGTVOS-090': Failed to launch the application 'net.dot.System.Buffers.Tests' on the device 'DNCENGTVOS-090': Application failed to launch: EThe operation couldnt be completed. [PBD] Denying open-application request for reason: Disabled (Cannot launch app 'net.dot.System.Buffers.Tests' while Setup Assistant is running)"); log.WriteLine("[08:44:11.3887100] "); log.WriteLine("[08:44:11.3887170] . You can still launch the application manually by tapping on it."); log.WriteLine("[08:44:11.3887220] "); log.WriteLine("[08:44:11.3887260] --- inner exception"); log.WriteLine("[08:44:11.3887310] error MT1020: Failed to launch the application 'net.dot.System.Buffers.Tests' on the device 'DNCENGTVOS-090': Application failed to launch: EThe operation couldnt be completed. [PBD] Denying open-application request for reason: Disabled (Cannot launch app 'net.dot.System.Buffers.Tests' while Setup Assistant is running)"); log.WriteLine("[08:44:11.3887360] "); log.WriteLine("[08:44:11.3887400] "); log.WriteLine("[08:44:11.3887450] ---"); log.WriteLine("[08:44:11.3887490] --- inner exception"); log.WriteLine("[08:44:11.3887570] error HE1107: Application failed to launch: EThe operation couldnt be completed. [PBD] Denying open-application request for reason: Disabled (Cannot launch app 'net.dot.System.Buffers.Tests' while Setup Assistant is running)"); log.WriteLine("[08:44:11.3887790] "); log.WriteLine("[08:44:11.3887840] ---"); log.WriteLine("[08:44:11.3917790] at Xamarin.Launcher.DevController.LaunchDeviceBundleIdAsync (System.String bundle_id, Xamarin.Hosting.DeviceLaunchConfig config) [0x001cc] in /Users/builder/azdo/_work/1/s/maccore/tools/mlaunch/Xamarin.Hosting/Xamarin.Launcher/controller-device.cs:208 "); log.WriteLine("[08:44:11.3917960] at Xamarin.Utils.NSRunLoopExtensions.RunUntilTaskCompletion[T] (Foundation.NSRunLoop this, System.Threading.Tasks.Task`1[TResult] task) [0x00082] in /Users/builder/azdo/_work/1/s/maccore/tools/mlaunch/Xamarin.Hosting/Xamarin.Utils/Extensions.cs:35 "); log.WriteLine("[08:44:11.3918040] at Xamarin.Launcher.Driver.Main2 (System.String[] args) [0x00b43] in /Users/builder/azdo/_work/1/s/maccore/tools/mlaunch/Xamarin.Hosting/Xamarin.Launcher/Main.cs:459 "); log.WriteLine("[08:44:11.3918090] at Xamarin.Launcher.Driver.Main (System.String[] args) [0x0006d] in /Users/builder/azdo/_work/1/s/maccore/tools/mlaunch/Xamarin.Hosting/Xamarin.Launcher/Main.cs:151 "); log.Flush(); Assert.True(_errorKnowledgeBase.IsKnownTestIssue(log, out var failure)); Assert.Equal(expectedFailureMessage, failure?.HumanMessage); } } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/ExitCodeDetectorTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Text; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests; public class ExitCodeDetectorTests : IDisposable { private readonly string? _tempFilename = null; [Fact] public void ExitCodeIsDetectedTest() { var appBundleInformation = new AppBundleInformation("HelloiOS", "net.dot.HelloiOS", "some/path", "some/path", false, null); var detector = new iOSExitCodeDetector(); var log = new[] { "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class MockAKSRefKeyObject is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b738) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x103259970). One of the two will be used. Which one is undefined.", "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class MockAKSOptionalParameters is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b788) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x1032599c0). One of the two will be used. Which one is undefined.", "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class SecXPCHelper is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b918) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x103259a60). One of the two will be used. Which one is undefined.", "Nov 18 04:31:44 ML-MacVM com.apple.CoreSimulator.SimDevice.2E1EE736-5672-4220-89B5-B7C77DB6AF18[55655] (UIKitApplication:net.dot.HelloiOS[9a0b][rb-legacy][57331]): Service exited with abnormal code: 200", "Nov 18 04:31:44 dci-mac-build-053 com.apple.CoreSimulator.SimDevice.F67392D9-A327-4217-B924-5DA0918415E5[811] (com.apple.security.cloudkeychainproxy3[67121]): Service exited with abnormal code: 1", "Nov 18 04:31:44 dci-mac-build-053 com.apple.CoreSimulator.SimDevice.F67392D9-A327-4217-B924-5DA0918415E5[811] (com.apple.security.cloudkeychainproxy3): Service only ran for 0 seconds. Pushing respawn out by 10 seconds.", }; var exitCode = detector.DetectExitCode(appBundleInformation, GetLogMock(log)); Assert.Equal(200, exitCode); } [Fact] public void ExitCodeIsDetectedOnMacCatalystTest() { var appBundleInformation = new AppBundleInformation("System.Buffers.Tests", "net.dot.System.Buffers.Tests", "some/path", "some/path", false, null); var detector = new MacCatalystExitCodeDetector(); var log = new[] { "Feb 18 06:40:16 Admins-Mac-Mini System.Buffers.Tests[59229]: CDN - client insert callback function client = 0 type = 17 function = 0x7fff3b262246 local_olny = false", "Feb 18 06:40:16 Admins-Mac-Mini System.Buffers.Tests[59229]: CDN - client setup_remote_port", "Feb 18 06:40:16 Admins-Mac-Mini System.Buffers.Tests[59229]: CDN - Bootstrap Port: 1799", "Feb 18 06:40:16 Admins-Mac-Mini System.Buffers.Tests[59229]: CDN - Remote Port: 56835 (com.apple.CoreDisplay.Notification)", "Feb 18 06:40:16 Admins-Mac-Mini System.Buffers.Tests[59229]: CDN - client setup_local_port", "Feb 18 06:40:16 Admins-Mac-Mini System.Buffers.Tests[59229]: CDN - Local Port: 78339", "Feb 18 06:40:16 Admins-Mac-Mini com.apple.xpc.launchd[1] (net.dot.System.Buffers.Tests.15140[59229]): Service exited with abnormal code: 74", "Feb 18 06:40:49 Admins-Mac-Mini com.apple.xpc.launchd[1] (com.apple.mdworker.shared.09000000-0600-0000-0000-000000000000[59231]): Service exited due to SIGKILL | sent by mds[88]", "Feb 18 06:40:58 Admins-Mac-Mini com.apple.xpc.launchd[1] (com.apple.mdworker.shared.02000000-0100-0000-0000-000000000000[59232]): Service exited due to SIGKILL | sent by mds[88]", "Feb 18 06:41:01 Admins-Mac-Mini com.apple.xpc.launchd[1] (com.apple.mdworker.shared.0D000000-0000-0000-0000-000000000000[59237]): Service exited due to SIGKILL | sent by mds[88]", "Feb 18 06:41:23 Admins-Mac-Mini System.Buffers.Tests[59248]: CDN - client insert callback function client = 0 type = 17 function = 0x7fff3b262246 local_olny = false", "Feb 18 06:41:23 Admins-Mac-Mini System.Buffers.Tests[59248]: CDN - client setup_remote_port", "Feb 18 06:41:23 Admins-Mac-Mini System.Buffers.Tests[59248]: CDN - Bootstrap Port: 1799", "Feb 18 06:41:23 Admins-Mac-Mini System.Buffers.Tests[59248]: CDN - Remote Port: 75271 (com.apple.CoreDisplay.Notification)", "Feb 18 06:41:23 Admins-Mac-Mini System.Buffers.Tests[59248]: CDN - client setup_local_port", "Feb 18 06:41:23 Admins-Mac-Mini System.Buffers.Tests[59248]: CDN - Local Port: 52995", }; var exitCode = detector.DetectExitCode(appBundleInformation, GetLogMock(log)); Assert.Equal(74, exitCode); } [Fact] public void NegativeExitCodeIsDetectedTest() { var appBundleInformation = new AppBundleInformation("HelloiOS", "net.dot.HelloiOS", "some/path", "some/path", false, null); var detector = new iOSExitCodeDetector(); var log = new[] { "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class MockAKSRefKeyObject is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b738) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x103259970). One of the two will be used. Which one is undefined.", "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class MockAKSOptionalParameters is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b788) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x1032599c0). One of the two will be used. Which one is undefined.", "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class SecXPCHelper is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b918) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x103259a60). One of the two will be used. Which one is undefined.", "Nov 18 04:31:44 ML-MacVM com.apple.CoreSimulator.SimDevice.2E1EE736-5672-4220-89B5-B7C77DB6AF18[55655] (UIKitApplication:net.dot.HelloiOS[9a0b][rb-legacy][57331]): Service exited with abnormal code: -2", "Nov 18 04:31:44 dci-mac-build-053 com.apple.CoreSimulator.SimDevice.F67392D9-A327-4217-B924-5DA0918415E5[811] (com.apple.security.cloudkeychainproxy3[67121]): Service exited with abnormal code: 1", "Nov 18 04:31:44 dci-mac-build-053 com.apple.CoreSimulator.SimDevice.F67392D9-A327-4217-B924-5DA0918415E5[811] (com.apple.security.cloudkeychainproxy3): Service only ran for 0 seconds. Pushing respawn out by 10 seconds.", }; var exitCode = detector.DetectExitCode(appBundleInformation, GetLogMock(log)); Assert.Equal(-2, exitCode); } [Fact] public void iOSDeviceCodeIsDetectedTest() { var appBundleInformation = new AppBundleInformation("iOS.Simulator.PInvoke.Test", "net.dot.iOS.Simulator.PInvoke.Test", "some/path", "some/path", false, null); var detector = new iOSExitCodeDetector(); var log = new[] { "[07:02:15.9749990] Xamarin.Hosting: Mounting developer image on 'DNCENGOSX-003'", "[07:02:15.9752160] Xamarin.Hosting: Mounted developer image on 'DNCENGOSX-003'", "[07:02:16.5177370] Xamarin.Hosting: Launched net.dot.Some.Other.App with PID: 13942", "[07:02:16.5181560] Launched application 'net.dot.Some.Other.App' on 'DNCENGOSX-003' with pid 13942", "[07:02:16.6150270] 2022-03-30 07:02:16.601 Some.Other.App[13942:136284382] Done!", "[07:02:21.6632630] Xamarin.Hosting: Process '13942' exited with exit code 143 or crashing signal .", "[07:02:21.6637600] Application 'net.dot.Some.Other.App' terminated (with exit code '143' and/or crashing signal ').", // We care about this run "[07:02:15.9749990] Xamarin.Hosting: Mounting developer image on 'DNCENGOSX-003'", "[07:02:15.9752160] Xamarin.Hosting: Mounted developer image on 'DNCENGOSX-003'", "[07:02:16.5177370] Xamarin.Hosting: Launched net.dot.iOS.Simulator.PInvoke.Test with PID: 83937", "[07:02:16.5181560] Launched application 'net.dot.iOS.Simulator.PInvoke.Test' on 'DNCENGOSX-003' with pid 83937", "[07:02:16.6150270] 2022-03-30 07:02:16.601 iOS.Simulator.PInvoke.Test[83937:136284382] Done!", "[07:02:21.6632630] Xamarin.Hosting: Process '83937' exited with exit code 42 or crashing signal .", "[07:02:21.6637600] Application 'net.dot.iOS.Simulator.PInvoke.Test' terminated (with exit code '42' and/or crashing signal ').", }; var exitCode = detector.DetectExitCode(appBundleInformation, GetLogMock(log)); Assert.Equal(42, exitCode); } [Fact] public void ExitCodeIsNotDetectedTest() { var appBundleInformation = new AppBundleInformation("HelloiOS", "net.dot.HelloiOS", "some/path", "some/path", false, null); var detector = new iOSExitCodeDetector(); var log = new[] { "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class MockAKSRefKeyObject is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b738) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x103259970). One of the two will be used. Which one is undefined.", "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class MockAKSOptionalParameters is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b788) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x1032599c0). One of the two will be used. Which one is undefined.", "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class SecXPCHelper is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b918) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x103259a60). One of the two will be used. Which one is undefined.", "Nov 18 04:31:44 ML-MacVM com.apple.CoreSimulator.SimDevice.2E1EE736-5672-4220-89B5-B7C77DB6AF18[55655] (UIKitApplication:net.dot.HelloiOS[9a0b][rb-legacy][57331]): Some other error message", "Nov 18 04:31:44 dci-mac-build-053 com.apple.CoreSimulator.SimDevice.F67392D9-A327-4217-B924-5DA0918415E5[811] (com.apple.security.cloudkeychainproxy3[67121]): Service exited with abnormal code: 1", "Nov 18 04:31:44 dci-mac-build-053 com.apple.CoreSimulator.SimDevice.F67392D9-A327-4217-B924-5DA0918415E5[811] (com.apple.security.cloudkeychainproxy3): Service only ran for 0 seconds. Pushing respawn out by 10 seconds.", }; var exitCode = detector.DetectExitCode(appBundleInformation, GetLogMock(log)); Assert.Null(exitCode); } [Fact] public void ExitCodeFromPreviousRunIsIgnored() { var previousLog = "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class MockAKSRefKeyObject is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b738) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x103259970). One of the two will be used. Which one is undefined." + Environment.NewLine + "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class MockAKSOptionalParameters is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b788) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x1032599c0). One of the two will be used. Which one is undefined." + Environment.NewLine + "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class SecXPCHelper is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b918) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x103259a60). One of the two will be used. Which one is undefined." + Environment.NewLine + "Nov 18 04:31:44 dci-mac-build-053 ML-MacVM com.apple.CoreSimulator.SimDevice.2E1EE736-5672-4220-89B5-B7C77DB6AF18[55655] (UIKitApplication:net.dot.HelloiOS[9a0b][rb-legacy][57331]): Some other error message" + Environment.NewLine + "Nov 18 04:31:44 dci-mac-build-053 ML-MacVM com.apple.CoreSimulator.SimDevice.2E1EE736-5672-4220-89B5-B7C77DB6AF18[55655] (UIKitApplication:net.dot.HelloiOS[9a0b][rb-legacy][57331]): Service exited with abnormal code: 55" + Environment.NewLine + "Nov 18 04:31:44 dci-mac-build-053 com.apple.CoreSimulator.SimDevice.F67392D9-A327-4217-B924-5DA0918415E5[811] (com.apple.security.cloudkeychainproxy3): Service only ran for 0 seconds. Pushing respawn out by 10 seconds." + Environment.NewLine; var currentRunLog = "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class MockAKSRefKeyObject is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b738) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x103259970). One of the two will be used. Which one is undefined." + Environment.NewLine + "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class MockAKSOptionalParameters is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b788) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x1032599c0). One of the two will be used. Which one is undefined." + Environment.NewLine + "Nov 18 04:31:44 dci-mac-build-053 CloudKeychainProxy[67121]: objc[67121]: Class SecXPCHelper is implemented in both /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/Security (0x10350b918) and /Applications/Xcode115.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Security.framework/CloudKeychainProxy.bundle/CloudKeychainProxy (0x103259a60). One of the two will be used. Which one is undefined." + Environment.NewLine + "Nov 18 04:31:44 dci-mac-build-053 ML-MacVM com.apple.CoreSimulator.SimDevice.2E1EE736-5672-4220-89B5-B7C77DB6AF18[55655] (UIKitApplication:net.dot.HelloiOS[9a0b][rb-legacy][57331]): Some other error message" + Environment.NewLine + "Nov 18 04:31:44 dci-mac-build-053 ML-MacVM com.apple.CoreSimulator.SimDevice.2E1EE736-5672-4220-89B5-B7C77DB6AF18[55655] (UIKitApplication:net.dot.HelloiOS[9a0b][rb-legacy][57331]): Service exited with abnormal code: 72" + Environment.NewLine + "Nov 18 04:31:44 dci-mac-build-053 com.apple.CoreSimulator.SimDevice.F67392D9-A327-4217-B924-5DA0918415E5[811] (com.apple.security.cloudkeychainproxy3): Service only ran for 0 seconds. Pushing respawn out by 10 seconds." + Environment.NewLine; var tempFilename = Path.GetTempFileName(); File.WriteAllText(tempFilename, previousLog); var capturedFilename = Path.GetTempFileName(); using var captureLog = new CaptureLog(capturedFilename, tempFilename, false); captureLog.StartCapture(); File.AppendAllText(tempFilename, currentRunLog); captureLog.StopCapture(); var appBundleInformation = new AppBundleInformation("net.dot.HelloiOS", "net.dot.HelloiOS", "some/path", "some/path", false, null); var exitCode = new iOSExitCodeDetector().DetectExitCode(appBundleInformation, captureLog); Assert.Equal(72, exitCode); } public void Dispose() { if (_tempFilename != null) { File.Delete(_tempFilename); } GC.SuppressFinalize(this); } private static IFileBackedLog GetLogMock(string[] loglines) { byte[] byteArray = Encoding.ASCII.GetBytes(string.Join(Environment.NewLine, loglines)); var stream = new MemoryStream(byteArray); var reader = new StreamReader(stream); var mock = new Mock(); mock.Setup(x => x.GetReader()).Returns(reader); return mock.Object; } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/Microsoft.DotNet.XHarness.Apple.Tests.csproj ================================================  $(NetCurrent) ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/MockLogs.cs ================================================ using System.Collections.Generic; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Moq; namespace Microsoft.DotNet.XHarness.Apple.Tests; #nullable disable public class MockLogs : List, ILogs { public string Directory { get; set; } = "/tmp/logs/"; public IFileBackedLog AddFile(string path) => AddFile(path, path); public IFileBackedLog AddFile(string path, string name) { var log = Mock.Of(x => x.FullPath == path && x.Description == name); Add(log); return log; } public string CreateFile(string path, string description) { var log = Mock.Of(x => x.FullPath == path && x.Description == description); Add(log); return Directory + path; } public IFileBackedLog Create(string filename, string name, bool? timestamp = null) => AddFile(filename, name); public string CreateFile(string path, LogType type) => CreateFile(path, type.ToString()); public void Dispose() { } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/Orchestration/CopyLogsToMainLogTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.Orchestration; public class CopyLogsToMainLogTests : OrchestratorTestBase { private readonly TestOrchestrator _testOrchestrator; private readonly Mock _appTester; private readonly Mock _appTesterFactory; private readonly List _mainLogLines; private const string SuccessResultLine = "Tests run: 10 Passed: 10 Inconclusive: 0 Failed: 0 Ignored: 0"; public CopyLogsToMainLogTests() { _appTester = new(); _appTesterFactory = new(); _appTesterFactory.SetReturnsDefault(_appTester.Object); _appInstaller.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, })); _appUninstaller.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, })); _mainLogLines = new List(); _mainLog .Setup(x => x.WriteLine(It.IsAny())) .Callback(line => _mainLogLines.Add(line)); _testOrchestrator = new( _appBundleInformationParser.Object, _appInstaller.Object, _appUninstaller.Object, _appTesterFactory.Object, _deviceFinder.Object, _logger.Object, _logs, _mainLog.Object, _errorKnowledgeBase.Object, _diagnosticsData, _helpers.Object); } [Fact] public async Task SimulatorTest_CopiesApplicationLogToMainLog() { // Setup: add an ApplicationLog with test output var appLogContent = "[PASS] MyTest.TestMethod1\n[PASS] MyTest.TestMethod2\n[FAIL] MyTest.TestMethod3\n"; AddLogWithContent(LogType.ApplicationLog, "net.dot.Tests.log", appLogContent); // Also add a SystemLog (should NOT be copied for simulators) AddLogWithContent(LogType.SystemLog, "simulator.system.log", "System noise that should not appear"); var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); _appTester .Setup(x => x.TestApp( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Succeeded, SuccessResultLine)); // Act var result = await _testOrchestrator.OrchestrateTest( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: false, resetSimulator: true, enableLldb: false, signalAppEnd: false, Array.Empty<(string, string?)>(), Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); var mainLogText = string.Join("\n", _mainLogLines); // ApplicationLog content should be present Assert.Contains("[PASS] MyTest.TestMethod1", mainLogText); Assert.Contains("[FAIL] MyTest.TestMethod3", mainLogText); Assert.Contains("==================== ApplicationLog ====================", mainLogText); Assert.Contains("==================== End of ApplicationLog ====================", mainLogText); // SystemLog content should NOT be present Assert.DoesNotContain("System noise that should not appear", mainLogText); } [Fact] public async Task MacCatalystTest_CopiesSystemLogToMainLog() { // Setup: reset mocks since MacCatalyst skips device finding, install, uninstall _appInstaller.Reset(); _appUninstaller.Reset(); _deviceFinder.Reset(); // Add a SystemLog with test output (this is where MacCatalyst output goes) var sysLogContent = "[PASS] MyMacTest.TestA\n[PASS] MyMacTest.TestB\n=== TEST EXECUTION SUMMARY ===\n"; AddLogWithContent(LogType.SystemLog, "MacCatalyst.system.log", sysLogContent); // Also add an ApplicationLog (should NOT be copied for MacCatalyst — it won't exist in practice) AddLogWithContent(LogType.ApplicationLog, "app.log", "App log content that should not appear"); var testTarget = new TestTargetOs(TestTarget.MacCatalyst, null); _appTester .Setup(x => x.TestMacCatalystApp( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Succeeded, SuccessResultLine)); // Act var result = await _testOrchestrator.OrchestrateTest( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: false, resetSimulator: true, enableLldb: false, signalAppEnd: true, Array.Empty<(string, string?)>(), Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); var mainLogText = string.Join("\n", _mainLogLines); // SystemLog content should be present (MacCatalyst uses SystemLog) Assert.Contains("[PASS] MyMacTest.TestA", mainLogText); Assert.Contains("TEST EXECUTION SUMMARY", mainLogText); Assert.Contains("==================== SystemLog ====================", mainLogText); // ApplicationLog content should NOT be present Assert.DoesNotContain("App log content that should not appear", mainLogText); } [Fact] public async Task DeviceTest_CopiesApplicationLogToMainLog() { // Setup: add an ApplicationLog with test output var appLogContent = "[PASS] DeviceTest.Test1\n[FAIL] DeviceTest.Test2\n"; AddLogWithContent(LogType.ApplicationLog, "net.dot.Tests.log", appLogContent); var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); _appTester .Setup(x => x.TestApp( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Succeeded, SuccessResultLine)); // Act var result = await _testOrchestrator.OrchestrateTest( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: false, resetSimulator: false, enableLldb: false, signalAppEnd: false, Array.Empty<(string, string?)>(), Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); var mainLogText = string.Join("\n", _mainLogLines); Assert.Contains("[PASS] DeviceTest.Test1", mainLogText); Assert.Contains("[FAIL] DeviceTest.Test2", mainLogText); Assert.Contains("==================== ApplicationLog ====================", mainLogText); } private void AddLogWithContent(LogType logType, string fileName, string content) { // Write content to a temp file so GetReader() works var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + "_" + fileName); File.WriteAllText(tempPath, content); var mockLog = new Mock(); mockLog.Setup(x => x.Description).Returns(logType.ToString()); mockLog.Setup(x => x.FullPath).Returns(tempPath); mockLog.Setup(x => x.GetReader()).Returns(() => new StreamReader(tempPath)); _logs.Add(mockLog.Object); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/Orchestration/InstallOrchestratorTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.Orchestration; public class InstallOrchestratorTests : OrchestratorTestBase { private readonly InstallOrchestrator _installOrchestrator; public InstallOrchestratorTests() { _installOrchestrator = new( _appInstaller.Object, _appUninstaller.Object, _appBundleInformationParser.Object, _deviceFinder.Object, _logger.Object, _logs, _mainLog.Object, _errorKnowledgeBase.Object, _diagnosticsData, _helpers.Object); } [Fact] public async Task OrchestrateSimulatorInstallationTest() { // Setup SetupInstall(_simulator.Object); SetupUninstall(_simulator.Object, 1); // This can fail as this is the first purge of the app before we install it var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); // Act var result = await _installOrchestrator.OrchestrateInstall( testTarget, null, AppPath, TimeSpan.FromMinutes(30), includeWirelessDevices: false, resetSimulator: false, enableLldb: false, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _simulator.Object, It.IsAny()), Times.Once); } [Fact] public async Task OrchestrateSimulatorInstallationWithResetTest() { // Setup SetupInstall(_simulator.Object); SetupUninstall(_simulator.Object, 1); // This can fail as this is the first purge of the app before we install it var testTarget = new TestTargetOs(TestTarget.Simulator_tvOS, "13.5"); // Act var result = await _installOrchestrator.OrchestrateInstall( testTarget, null, AppPath, TimeSpan.FromMinutes(30), includeWirelessDevices: true, resetSimulator: true, enableLldb: true, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), true, true, It.IsAny()), Times.Once); VerifySimulatorReset(true); VerifySimulatorCleanUp(false); // Install doesn't end with a cleanup so that the app stays behind VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _simulator.Object, It.IsAny()), Times.Once); } [Fact] public async Task OrchestrateDeviceInstallationTest() { // Setup SetupInstall(_device.Object); SetupUninstall(_device.Object, 1); // This can fail as this is the first purge of the app before we install it var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); // Act var result = await _installOrchestrator.OrchestrateInstall( testTarget, null, AppPath, TimeSpan.FromMinutes(30), includeWirelessDevices: false, resetSimulator: false, enableLldb: true, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _device.Object, It.IsAny()), Times.Once); } [Fact] public async Task OrchestrateFailedDeviceInstallationTest() { // Setup SetupInstall(_device.Object, 1); SetupUninstall(_device.Object, 1); // This can fail as this is the first purge of the app before we install it var failure = new KnownIssue("Some failure", suggestedExitCode: (int)ExitCode.APP_NOT_SIGNED); _errorKnowledgeBase .Setup(x => x.IsKnownInstallIssue(It.IsAny(), out failure)) .Returns(true) .Verifiable(); var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); // Act var result = await _installOrchestrator.OrchestrateInstall( testTarget, null, AppPath, TimeSpan.FromMinutes(30), includeWirelessDevices: false, resetSimulator: false, enableLldb: true, new CancellationToken()); // Verify Assert.Equal(ExitCode.APP_NOT_SIGNED, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _device.Object, It.IsAny()), Times.Once); } [Fact] public async Task OrchestrateMacCatalystInstallationTest() { // Setup _appInstaller.Reset(); _appUninstaller.Reset(); _deviceFinder.Reset(); var testTarget = new TestTargetOs(TestTarget.MacCatalyst, null); // Act var result = await _installOrchestrator.OrchestrateInstall( testTarget, null, AppPath, TimeSpan.FromMinutes(30), includeWirelessDevices: false, resetSimulator: false, enableLldb: true, new CancellationToken()); // Verify _deviceFinder.Verify( x => x.FindDevice(testTarget, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); _deviceFinder.VerifyNoOtherCalls(); } [Fact] public async Task OrchestrateDeviceInstallationWhenNoDeviceTest() { // Setup _deviceFinder.Reset(); _deviceFinder .Setup(x => x.FindDevice( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(new NoDeviceFoundException()); var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); // Act var result = await _installOrchestrator.OrchestrateInstall( testTarget, null, AppPath, TimeSpan.FromMinutes(30), includeWirelessDevices: false, resetSimulator: false, enableLldb: true, new CancellationToken()); // Verify Assert.Equal(ExitCode.DEVICE_NOT_FOUND, result); } private void SetupInstall(IDevice device, int exitCode = 0) { _appInstaller .Setup(x => x.InstallApp(_appBundleInformation, It.IsAny(), device, It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = exitCode, TimedOut = false, }); } private void SetupUninstall(IDevice device, int exitCode = 0) { if (device is ISimulatorDevice simulator) { _appUninstaller .Setup(x => x.UninstallSimulatorApp(simulator, BundleIdentifier, It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = exitCode, TimedOut = false, }); } else if (device is IHardwareDevice phone) { _appUninstaller .Setup(x => x.UninstallDeviceApp(phone, BundleIdentifier, It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = exitCode, TimedOut = false, }); } } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/Orchestration/JustRunOrchestratorTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.Orchestration; public class JustRunOrchestratorTests : OrchestratorTestBase { private readonly IJustRunOrchestrator _justRunOrchestrator; private readonly Mock _iOSExitCodeDetector; private readonly Mock _macCatalystExitCodeDetector; private readonly Mock _appRunner; private readonly Mock _appRunnerFactory; public JustRunOrchestratorTests() { _iOSExitCodeDetector = new(); _macCatalystExitCodeDetector = new(); _appRunner = new(); _appRunnerFactory = new(); _appRunnerFactory.SetReturnsDefault(_appRunner.Object); // These two shouldn't get invoked at all _appInstaller.Reset(); _appUninstaller.Reset(); _justRunOrchestrator = new JustRunOrchestrator( _appBundleInformationParser.Object, _appInstaller.Object, _appUninstaller.Object, _appRunnerFactory.Object, _deviceFinder.Object, _iOSExitCodeDetector.Object, _macCatalystExitCodeDetector.Object, _logger.Object, _logs, _mainLog.Object, _errorKnowledgeBase.Object, _diagnosticsData, _helpers.Object); } [Fact] public async Task OrchestrateSimulatorJustRunTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); var envVars = new (string, string?)[] { ("envVar1", "value1"), ("envVar2", "value2") }; _iOSExitCodeDetector .Setup(x => x.DetectExitCode(_appBundleInformation, It.IsAny())) .Returns(100) .Verifiable(); _appRunner .Setup(x => x.RunApp( _appBundleInformation, testTarget, _simulator.Object, null, TimeSpan.FromMinutes(30), false, true, It.IsAny>(), envVars, It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, }) .Verifiable(); // Act var result = await _justRunOrchestrator.OrchestrateRun( BundleIdentifier, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: false, enableLldb: false, signalAppEnd: false, waitForExit: true, envVars, Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _iOSExitCodeDetector.VerifyAll(); _appRunner.VerifyAll(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); _simulator.Verify(x => x.Boot(_mainLog.Object, It.IsAny()), Times.Once); _simulator.Verify(x => x.GetAppBundlePath(_mainLog.Object, BundleIdentifier, It.IsAny()), Times.Once); } [Fact] public async Task OrchestrateDeviceJustRunTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); _iOSExitCodeDetector .Setup(x => x.DetectExitCode(It.Is(info => info.BundleIdentifier == BundleIdentifier), It.IsAny())) .Returns(100) .Verifiable(); var extraArguments = new[] { "--some arg1", "--some arg2" }; _appRunner .Setup(x => x.RunApp( It.Is(info => info.BundleIdentifier == BundleIdentifier), testTarget, _device.Object, null, TimeSpan.FromMinutes(30), false, true, extraArguments, It.IsAny>(), It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, }) .Verifiable(); // Act var result = await _justRunOrchestrator.OrchestrateRun( BundleIdentifier, testTarget, DeviceName, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: true, enableLldb: false, signalAppEnd: false, waitForExit: true, Array.Empty<(string, string?)>(), extraArguments, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, DeviceName, It.IsAny(), true, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _iOSExitCodeDetector.VerifyAll(); _appRunner.VerifyAll(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } [Fact] public async Task OrchestrateFailedSimulatorJustRunTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); _appRunner .Setup(x => x.RunApp( _appBundleInformation, testTarget, _simulator.Object, null, TimeSpan.FromMinutes(30), false, true, It.IsAny>(), It.IsAny>(), It.IsAny())) .ThrowsAsync(new Exception()) .Verifiable(); var failure = new KnownIssue("Some failure", suggestedExitCode: (int)ExitCode.SIMULATOR_FAILURE); _errorKnowledgeBase .Setup(x => x.IsKnownTestIssue(It.IsAny(), out failure)) .Returns(true) .Verifiable(); // Act var result = await _justRunOrchestrator.OrchestrateRun( BundleIdentifier, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: false, enableLldb: true, signalAppEnd: false, waitForExit: true, Array.Empty<(string, string?)>(), Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.SIMULATOR_FAILURE, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _errorKnowledgeBase.VerifyAll(); _appRunner.VerifyAll(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); _simulator.Verify(x => x.Boot(_mainLog.Object, It.IsAny()), Times.Once); _simulator.Verify(x => x.GetAppBundlePath(_mainLog.Object, BundleIdentifier, It.IsAny()), Times.Once); } [Fact] public async Task OrchestrateFailedDeviceJustRunTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); _iOSExitCodeDetector .Setup(x => x.DetectExitCode(It.Is(info => info.BundleIdentifier == BundleIdentifier), It.IsAny())) .Returns(200) .Verifiable(); var extraArguments = new[] { "--some arg1", "--some arg2" }; _appRunner .Setup(x => x.RunApp( It.Is(info => info.BundleIdentifier == BundleIdentifier), testTarget, _device.Object, null, TimeSpan.FromMinutes(30), true, true, extraArguments, It.IsAny>(), It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, }) .Verifiable(); var failure = new KnownIssue("Some failure", suggestedExitCode: (int)ExitCode.DEVICE_FAILURE); _errorKnowledgeBase .Setup(x => x.IsKnownTestIssue(It.IsAny(), out failure)) .Returns(true) .Verifiable(); // Act var result = await _justRunOrchestrator.OrchestrateRun( BundleIdentifier, testTarget, DeviceName, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: true, enableLldb: false, signalAppEnd: true, waitForExit: true, Array.Empty<(string, string?)>(), extraArguments, new CancellationToken()); // Verify Assert.Equal(ExitCode.DEVICE_FAILURE, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, DeviceName, It.IsAny(), true, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _iOSExitCodeDetector.VerifyAll(); _appRunner.VerifyAll(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } [Fact] public async Task OrchestrateMacCatalystJustRunTest() { // Setup _appInstaller.Reset(); _appUninstaller.Reset(); _deviceFinder.Reset(); var testTarget = new TestTargetOs(TestTarget.MacCatalyst, null); var envVars = new (string, string?)[] { ("envVar1", "value1"), ("envVar2", "value2") }; _macCatalystExitCodeDetector .Setup(x => x.DetectExitCode(It.Is(info => info.BundleIdentifier == BundleIdentifier), It.IsAny())) .Returns(100) .Verifiable(); _appRunner .Setup(x => x.RunMacCatalystApp( It.Is(info => info.BundleIdentifier == BundleIdentifier), TimeSpan.FromMinutes(30), true, true, It.IsAny>(), envVars, It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, }) .Verifiable(); // Act var result = await _justRunOrchestrator.OrchestrateRun( BundleIdentifier, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: false, enableLldb: false, signalAppEnd: true, waitForExit: true, envVars, Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); _macCatalystExitCodeDetector.VerifyAll(); _appRunner.VerifyAll(); _deviceFinder.VerifyNoOtherCalls(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/Orchestration/JustTestOrchestratorTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.Orchestration; public class JustTestOrchestratorTests : OrchestratorTestBase { private readonly IJustTestOrchestrator _justTestOrchestrator; private readonly Mock _appTester; private readonly Mock _appTesterFactory; private const string SuccessResultLine = "Tests run: 1194 Passed: 1191 Inconclusive: 0 Failed: 0 Ignored: 0"; public JustTestOrchestratorTests() { _appTester = new(); _appTesterFactory = new(); _appTesterFactory.SetReturnsDefault(_appTester.Object); // These two shouldn't get invoked at all _appInstaller.Reset(); _appUninstaller.Reset(); _justTestOrchestrator = new JustTestOrchestrator( _appBundleInformationParser.Object, _appInstaller.Object, _appUninstaller.Object, _appTesterFactory.Object, _deviceFinder.Object, _logger.Object, _logs, _mainLog.Object, _errorKnowledgeBase.Object, _diagnosticsData, _helpers.Object); } [Fact] public async Task OrchestrateSimulatorJustTestTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); var envVars = new (string, string?)[] { ("envVar1", "value1"), ("envVar2", "value2") }; _appTester .Setup(x => x.TestApp( _appBundleInformation, testTarget, _simulator.Object, null, TimeSpan.FromMinutes(30), It.IsAny(), false, It.IsAny>(), envVars, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Succeeded, SuccessResultLine)) .Verifiable(); // Act var result = await _justTestOrchestrator.OrchestrateTest( BundleIdentifier, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: false, enableLldb: false, signalAppEnd: false, envVars, Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appTester.VerifyAll(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); _simulator.Verify(x => x.Boot(_mainLog.Object, It.IsAny()), Times.Once); _simulator.Verify(x => x.GetAppBundlePath(_mainLog.Object, BundleIdentifier, It.IsAny()), Times.Once); } [Fact] public async Task OrchestrateDeviceJustTestTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); var extraArguments = new[] { "--some arg1", "--some arg2" }; _appTester .Setup(x => x.TestApp( It.Is(info => info.BundleIdentifier == BundleIdentifier), testTarget, _device.Object, null, TimeSpan.FromMinutes(30), It.IsAny(), false, extraArguments, It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Succeeded, SuccessResultLine)) .Verifiable(); // Act var result = await _justTestOrchestrator.OrchestrateTest( BundleIdentifier, testTarget, DeviceName, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: true, enableLldb: false, signalAppEnd: false, Array.Empty<(string, string?)>(), extraArguments, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, DeviceName, It.IsAny(), true, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appTester.VerifyAll(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } [Fact] public async Task OrchestrateFailedSimulatorJustTestTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); _appTester .Setup(x => x.TestApp( _appBundleInformation, testTarget, _simulator.Object, null, TimeSpan.FromMinutes(30), It.IsAny(), false, It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Crashed, "App never reported back")) .Verifiable(); var failure = new KnownIssue("Some failure", suggestedExitCode: (int)ExitCode.APP_CRASH); _errorKnowledgeBase .Setup(x => x.IsKnownTestIssue(It.IsAny(), out failure)) .Returns(true) .Verifiable(); // Act var result = await _justTestOrchestrator.OrchestrateTest( BundleIdentifier, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: false, enableLldb: true, signalAppEnd: false, Array.Empty<(string, string?)>(), Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.APP_CRASH, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _errorKnowledgeBase.VerifyAll(); _appTester.VerifyAll(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); _simulator.Verify(x => x.Boot(_mainLog.Object, It.IsAny()), Times.Once); _simulator.Verify(x => x.GetAppBundlePath(_mainLog.Object, BundleIdentifier, It.IsAny()), Times.Once); } [Fact] public async Task OrchestrateFailedDeviceJustTestTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); var extraArguments = new[] { "--some arg1", "--some arg2" }; _appTester .Setup(x => x.TestApp( It.Is(info => info.BundleIdentifier == BundleIdentifier), testTarget, _device.Object, null, TimeSpan.FromMinutes(30), It.IsAny(), true, extraArguments, It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.TimedOut, SuccessResultLine)) .Verifiable(); // Act var result = await _justTestOrchestrator.OrchestrateTest( BundleIdentifier, testTarget, DeviceName, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: true, enableLldb: false, signalAppEnd: true, Array.Empty<(string, string?)>(), extraArguments, new CancellationToken()); // Verify Assert.Equal(ExitCode.TIMED_OUT, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, DeviceName, It.IsAny(), true, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appTester.VerifyAll(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } [Fact] public async Task OrchestrateMacCatalystJustTestTest() { // Setup _appInstaller.Reset(); _appUninstaller.Reset(); _deviceFinder.Reset(); var testTarget = new TestTargetOs(TestTarget.MacCatalyst, null); var envVars = new (string, string?)[] { ("envVar1", "value1"), ("envVar2", "value2") }; _appTester .Setup(x => x.TestMacCatalystApp( It.Is(info => info.BundleIdentifier == BundleIdentifier), TimeSpan.FromMinutes(30), It.IsAny(), true, It.IsAny>(), envVars, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Failed, "Tests failed")) .Verifiable(); // Act var result = await _justTestOrchestrator.OrchestrateTest( BundleIdentifier, testTarget, null, TimeSpan.FromMinutes(30), It.IsAny(), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: false, enableLldb: false, signalAppEnd: true, envVars, Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.TESTS_FAILED, result); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); _appTester.VerifyAll(); _deviceFinder.VerifyNoOtherCalls(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/Orchestration/OrchestratorTestBase.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.IO; using System.Threading; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.Orchestration; public abstract class OrchestratorTestBase { protected const string UDID = "8A450AA31EA94191AD6B02455F377CC1"; protected const string SimulatorName = "iPhone X (13.5) - created by xharness"; protected const string DeviceName = "iPhone X (14.4)"; protected const string AppName = "System.Buffers.Tests"; protected const string AppPath = "/tmp/apps/System.Buffers.Tests.app"; protected const string BundleIdentifier = "net.dot.System.Buffers.Tests"; protected const string BundleExecutable = "System.Buffers.Tests"; protected readonly Mock _deviceFinder; protected readonly Mock _simulator; protected readonly Mock _device; protected readonly Mock _appBundleInformationParser; protected readonly Mock _errorKnowledgeBase; protected readonly Mock _mainLog; protected readonly Mock _logger; protected readonly Mock _helpers; protected readonly Mock _appInstaller; protected readonly Mock _appUninstaller; protected readonly AppBundleInformation _appBundleInformation; protected readonly IDiagnosticsData _diagnosticsData; protected readonly MockLogs _logs; public OrchestratorTestBase() { _logger = new(); _mainLog = new(); _helpers = new(); _errorKnowledgeBase = new(); _appInstaller = new(); _appUninstaller = new(); _logs = new(); _logs.AddFile("system.log", LogType.SystemLog.ToString()); _simulator = new(); _simulator.Setup(x => x.UDID).Returns(UDID); _simulator.Setup(x => x.Name).Returns(SimulatorName); _simulator.Setup(x => x.OSVersion).Returns("13.5"); _simulator.Setup(x => x.Boot(It.IsAny(), It.IsAny())).ReturnsAsync(true); _simulator.Setup(x => x.GetAppBundlePath(It.IsAny(), BundleIdentifier, It.IsAny())).ReturnsAsync(AppPath); _device = new(); _device.Setup(x => x.UDID).Returns(UDID); _device.Setup(x => x.Name).Returns(DeviceName); _device.Setup(x => x.OSVersion).Returns("14.2"); _appBundleInformation = new AppBundleInformation( appName: AppName, bundleIdentifier: BundleIdentifier, appPath: AppPath, launchAppPath: AppPath, supports32b: false, extension: null, bundleExecutable: BundleExecutable); _appBundleInformationParser = new Mock(); _appBundleInformationParser .Setup(x => x.ParseFromAppBundle( Path.GetFullPath(AppPath), It.IsAny(), _mainLog.Object, It.IsAny())) .ReturnsAsync(_appBundleInformation); _diagnosticsData = new CommandDiagnostics(Mock.Of(), TargetPlatform.Apple, "install"); _deviceFinder = new Mock(); _deviceFinder .Setup(x => x.FindDevice( It.Is(t => t.Platform.IsSimulator()), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new DevicePair(_simulator.Object, null)); _deviceFinder .Setup(x => x.FindDevice( It.Is(t => !t.Platform.IsSimulator()), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new DevicePair(_device.Object, null)); } protected void VerifySimulatorReset(bool shouldBeReset) { _simulator.Verify( x => x.PrepareSimulator(It.IsAny(), It.IsAny()), shouldBeReset ? Times.Once : Times.Never); } protected void VerifySimulatorCleanUp(bool shouldBeCleanedUp) { _simulator.Verify( x => x.KillEverything(It.IsAny()), shouldBeCleanedUp ? Times.Once : Times.Never); } protected void VerifyDiagnosticData(TestTargetOs target) { Assert.Equal(target.Platform.IsSimulator() ? _simulator.Object.Name : _device.Object.Name, _diagnosticsData.Device); Assert.Contains(target.OSVersion!, _diagnosticsData.TargetOS); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/Orchestration/RunOrchestratorTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.Orchestration; public class RunOrchestratorTests : OrchestratorTestBase { private readonly RunOrchestrator _runOrchestrator; private readonly Mock _iOSExitCodeDetector; private readonly Mock _macCatalystExitCodeDetector; private readonly Mock _appRunner; private readonly Mock _appRunnerFactory; public RunOrchestratorTests() { _iOSExitCodeDetector = new(); _macCatalystExitCodeDetector = new(); _appRunner = new(); _appRunnerFactory = new(); _appRunnerFactory.SetReturnsDefault(_appRunner.Object); // Prepare succeeding install/uninstall as we don't care about those in the test/run tests _appInstaller.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, })); _appUninstaller.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, })); _runOrchestrator = new( _appBundleInformationParser.Object, _appInstaller.Object, _appUninstaller.Object, _appRunnerFactory.Object, _deviceFinder.Object, _iOSExitCodeDetector.Object, _macCatalystExitCodeDetector.Object, _logger.Object, _logs, _mainLog.Object, _errorKnowledgeBase.Object, _diagnosticsData, _helpers.Object); } [Fact] public async Task OrchestrateSimulatorRunTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); var envVars = new (string, string?)[] { ("envVar1", "value1"), ("envVar2", "value2") }; _iOSExitCodeDetector .Setup(x => x.DetectExitCode(_appBundleInformation, It.IsAny())) .Returns(100) .Verifiable(); _appRunner .Setup(x => x.RunApp( _appBundleInformation, testTarget, _simulator.Object, null, TimeSpan.FromMinutes(30), false, true, It.IsAny>(), envVars, It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, }) .Verifiable(); // Act var result = await _runOrchestrator.OrchestrateRun( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: false, resetSimulator: true, enableLldb: false, signalAppEnd: false, waitForExit: true, envVars, Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(true); VerifySimulatorCleanUp(true); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _simulator.Object, It.IsAny()), Times.Once); _iOSExitCodeDetector.VerifyAll(); _appRunner.VerifyAll(); } [Fact] public async Task OrchestrateDeviceRunTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); _iOSExitCodeDetector .Setup(x => x.DetectExitCode(_appBundleInformation, It.IsAny())) .Returns(100) .Verifiable(); var extraArguments = new[] { "--some arg1", "--some arg2" }; _appRunner .Setup(x => x.RunApp( _appBundleInformation, testTarget, _device.Object, null, TimeSpan.FromMinutes(30), false, true, extraArguments, It.IsAny>(), It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, }) .Verifiable(); // Act var result = await _runOrchestrator.OrchestrateRun( AppPath, testTarget, DeviceName, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: true, resetSimulator: false, enableLldb: false, signalAppEnd: false, waitForExit: true, Array.Empty<(string, string?)>(), extraArguments, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, DeviceName, It.IsAny(), true, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _device.Object, It.IsAny()), Times.Once); _iOSExitCodeDetector.VerifyAll(); _appRunner.VerifyAll(); } [Fact] public async Task OrchestrateFailedSimulatorRunTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); _appRunner .Setup(x => x.RunApp( _appBundleInformation, testTarget, _simulator.Object, null, TimeSpan.FromMinutes(30), false, true, It.IsAny>(), It.IsAny>(), It.IsAny())) .ThrowsAsync(new Exception()) .Verifiable(); var failure = new KnownIssue("Some failure", suggestedExitCode: (int)ExitCode.SIMULATOR_FAILURE); _errorKnowledgeBase .Setup(x => x.IsKnownTestIssue(It.IsAny(), out failure)) .Returns(true) .Verifiable(); // Act var result = await _runOrchestrator.OrchestrateRun( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: false, resetSimulator: false, enableLldb: true, signalAppEnd: false, waitForExit: true, Array.Empty<(string, string?)>(), Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.SIMULATOR_FAILURE, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _simulator.Object, It.IsAny()), Times.Once); _errorKnowledgeBase.VerifyAll(); _appRunner.VerifyAll(); } [Fact] public async Task OrchestrateFailedDeviceRunTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); _iOSExitCodeDetector .Setup(x => x.DetectExitCode(_appBundleInformation, It.IsAny())) .Returns(200) .Verifiable(); var extraArguments = new[] { "--some arg1", "--some arg2" }; _appRunner .Setup(x => x.RunApp( _appBundleInformation, testTarget, _device.Object, null, TimeSpan.FromMinutes(30), true, true, extraArguments, It.IsAny>(), It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, }) .Verifiable(); var failure = new KnownIssue("Some failure", suggestedExitCode: (int)ExitCode.DEVICE_FAILURE); _errorKnowledgeBase .Setup(x => x.IsKnownTestIssue(It.IsAny(), out failure)) .Returns(true) .Verifiable(); // Act var result = await _runOrchestrator.OrchestrateRun( AppPath, testTarget, DeviceName, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: true, resetSimulator: false, enableLldb: false, signalAppEnd: true, waitForExit: true, Array.Empty<(string, string?)>(), extraArguments, new CancellationToken()); // Verify Assert.Equal(ExitCode.DEVICE_FAILURE, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, DeviceName, It.IsAny(), true, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _device.Object, It.IsAny()), Times.Once); _iOSExitCodeDetector.VerifyAll(); _appRunner.VerifyAll(); } [Fact] public async Task OrchestrateMacCatalystRunTest() { // Setup _appInstaller.Reset(); _appUninstaller.Reset(); _deviceFinder.Reset(); var testTarget = new TestTargetOs(TestTarget.MacCatalyst, null); var envVars = new (string, string?)[] { ("envVar1", "value1"), ("envVar2", "value2") }; _macCatalystExitCodeDetector .Setup(x => x.DetectExitCode(_appBundleInformation, It.IsAny())) .Returns(100) .Verifiable(); _appRunner .Setup(x => x.RunMacCatalystApp( _appBundleInformation, TimeSpan.FromMinutes(30), true, true, It.IsAny>(), envVars, It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, }) .Verifiable(); // Act var result = await _runOrchestrator.OrchestrateRun( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: false, resetSimulator: true, enableLldb: false, signalAppEnd: true, waitForExit: true, envVars, Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); _macCatalystExitCodeDetector.VerifyAll(); _appRunner.VerifyAll(); _deviceFinder.VerifyNoOtherCalls(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } [Fact] public async Task OrchestrateMacCatalystRunWithNoExitCodeTest() { // Setup _appInstaller.Reset(); _appUninstaller.Reset(); _deviceFinder.Reset(); var testTarget = new TestTargetOs(TestTarget.MacCatalyst, null); var envVars = new (string, string?)[] { ("envVar1", "value1"), ("envVar2", "value2") }; _macCatalystExitCodeDetector .Setup(x => x.DetectExitCode(_appBundleInformation, It.IsAny())) .Returns((int?)null) .Verifiable(); _appRunner .Setup(x => x.RunMacCatalystApp( _appBundleInformation, TimeSpan.FromMinutes(30), true, true, It.IsAny>(), envVars, It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, }) .Verifiable(); // Act var result = await _runOrchestrator.OrchestrateRun( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: false, resetSimulator: true, enableLldb: false, signalAppEnd: true, waitForExit: true, envVars, Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.RETURN_CODE_NOT_SET, result); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); _macCatalystExitCodeDetector.VerifyAll(); _appRunner.VerifyAll(); _deviceFinder.VerifyNoOtherCalls(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } [Fact] public async Task OrchestrateNoWaitDeviceRunTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); var extraArguments = new[] { "--some arg1", "--some arg2" }; _appRunner .Setup(x => x.RunApp( _appBundleInformation, testTarget, _device.Object, null, TimeSpan.FromMinutes(30), false, false, extraArguments, It.IsAny>(), It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, }) .Verifiable(); // Act var result = await _runOrchestrator.OrchestrateRun( AppPath, testTarget, DeviceName, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: true, resetSimulator: true, enableLldb: false, signalAppEnd: false, waitForExit: false, Array.Empty<(string, string?)>(), extraArguments, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, DeviceName, It.IsAny(), true, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _device.Object, It.IsAny()), Times.Once); _appUninstaller.Verify( x => x.UninstallDeviceApp(_device.Object, BundleIdentifier, It.IsAny()), Times.Once); // Once in preparation, but not a second time after we're done _appRunner.VerifyAll(); _iOSExitCodeDetector.VerifyNoOtherCalls(); } [Fact] public async Task OrchestrateNoWaitSimulatorRunTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); var extraArguments = new[] { "--some arg1", "--some arg2" }; _appRunner .Setup(x => x.RunApp( _appBundleInformation, testTarget, _simulator.Object, null, TimeSpan.FromMinutes(30), false, false, extraArguments, It.IsAny>(), It.IsAny())) .ReturnsAsync(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, }) .Verifiable(); // Act var result = await _runOrchestrator.OrchestrateRun( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), expectedExitCode: 100, includeWirelessDevices: true, resetSimulator: true, enableLldb: false, signalAppEnd: false, waitForExit: false, Array.Empty<(string, string?)>(), extraArguments, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), true, true, It.IsAny()), Times.Once); VerifySimulatorReset(true); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _simulator.Object, It.IsAny()), Times.Once); _appUninstaller.Verify( x => x.UninstallSimulatorApp(_simulator.Object, BundleIdentifier, It.IsAny()), Times.Never); // No preparation uninstall (because of reset), and then not at the end _appRunner.VerifyAll(); _iOSExitCodeDetector.VerifyNoOtherCalls(); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/Orchestration/SimulatorResetOrchestratorTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.Orchestration; public class SimulatorResetOrchestratorTests : OrchestratorTestBase { private readonly SimulatorResetOrchestrator _simulatorResetOrchestrator; public SimulatorResetOrchestratorTests() { _simulatorResetOrchestrator = new( _appInstaller.Object, _appUninstaller.Object, _deviceFinder.Object, _logger.Object, _logs, _mainLog.Object, _errorKnowledgeBase.Object, _diagnosticsData, _helpers.Object); _appInstaller.Reset(); _appUninstaller.Reset(); } [Fact] public async Task OrchestrateSimulatorResetTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); // Act var result = await _simulatorResetOrchestrator.OrchestrateSimulatorReset( testTarget, SimulatorName, TimeSpan.FromMinutes(30), new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, SimulatorName, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(true); VerifySimulatorCleanUp(false); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } [Fact] public async Task TryDeviceResetTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Device_iOS, "13.5"); _deviceFinder.Reset(); // Act var result = await _simulatorResetOrchestrator.OrchestrateSimulatorReset( testTarget, null, TimeSpan.FromMinutes(30), new CancellationToken()); // Verify Assert.Equal(ExitCode.INVALID_ARGUMENTS, result); _deviceFinder.VerifyNoOtherCalls(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } [Fact] public async Task TryMacCatalystResetTest() { // Setup var testTarget = new TestTargetOs(TestTarget.MacCatalyst, null); _deviceFinder.Reset(); // Act var result = await _simulatorResetOrchestrator.OrchestrateSimulatorReset( testTarget, null, TimeSpan.FromMinutes(30), new CancellationToken()); // Verify Assert.Equal(ExitCode.INVALID_ARGUMENTS, result); _deviceFinder.VerifyNoOtherCalls(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/Orchestration/TestOrchestratorTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.Orchestration; public class TestOrchestratorTests : OrchestratorTestBase { private readonly TestOrchestrator _testOrchestrator; private readonly Mock _appTester; private readonly Mock _appTesterFactory; private const string SuccessResultLine = "Tests run: 1194 Passed: 1191 Inconclusive: 0 Failed: 0 Ignored: 0"; public TestOrchestratorTests() { _appTester = new(); _appTesterFactory = new(); _appTesterFactory.SetReturnsDefault(_appTester.Object); // Prepare succeeding install/uninstall as we don't care about those in the test/run tests _appInstaller.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, })); _appUninstaller.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, })); _testOrchestrator = new( _appBundleInformationParser.Object, _appInstaller.Object, _appUninstaller.Object, _appTesterFactory.Object, _deviceFinder.Object, _logger.Object, _logs, _mainLog.Object, _errorKnowledgeBase.Object, _diagnosticsData, _helpers.Object); } [Fact] public async Task OrchestrateSimulatorTestTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); var envVars = new (string, string?)[] { ("envVar1", "value1"), ("envVar2", "value2") }; _appTester .Setup(x => x.TestApp( _appBundleInformation, testTarget, _simulator.Object, null, TimeSpan.FromMinutes(30), It.IsAny(), false, It.IsAny>(), envVars, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Succeeded, SuccessResultLine)) .Verifiable(); // Act var result = await _testOrchestrator.OrchestrateTest( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: false, resetSimulator: true, enableLldb: false, signalAppEnd: false, envVars, Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(true); VerifySimulatorCleanUp(true); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _simulator.Object, It.IsAny()), Times.Once); _appTester.VerifyAll(); } [Fact] public async Task OrchestrateDeviceTestTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); var extraArguments = new[] { "--some arg1", "--some arg2" }; _appTester .Setup(x => x.TestApp( _appBundleInformation, testTarget, _device.Object, null, TimeSpan.FromMinutes(30), It.IsAny(), false, extraArguments, It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Succeeded, SuccessResultLine)) .Verifiable(); // Act var result = await _testOrchestrator.OrchestrateTest( AppPath, testTarget, DeviceName, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: true, resetSimulator: false, enableLldb: false, signalAppEnd: false, Array.Empty<(string, string?)>(), extraArguments, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, DeviceName, It.IsAny(), true, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _device.Object, It.IsAny()), Times.Once); _appTester.VerifyAll(); } [Fact] public async Task OrchestrateFailedSimulatorTestTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); _appTester .Setup(x => x.TestApp( _appBundleInformation, testTarget, _simulator.Object, null, TimeSpan.FromMinutes(30), It.IsAny(), false, It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Crashed, "App never reported back")) .Verifiable(); var failure = new KnownIssue("Some failure", suggestedExitCode: (int)ExitCode.APP_CRASH); _errorKnowledgeBase .Setup(x => x.IsKnownTestIssue(It.IsAny(), out failure)) .Returns(true) .Verifiable(); // Act var result = await _testOrchestrator.OrchestrateTest( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: false, resetSimulator: false, enableLldb: true, signalAppEnd: false, Array.Empty<(string, string?)>(), Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.APP_CRASH, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _simulator.Object, It.IsAny()), Times.Once); _errorKnowledgeBase.VerifyAll(); _appTester.VerifyAll(); } [Fact] public async Task OrchestrateTimedOutSimulatorTestTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); _appTester .Setup(x => x.TestApp( _appBundleInformation, testTarget, _simulator.Object, null, TimeSpan.FromMinutes(30), It.IsAny(), false, It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Crashed, "App never reported back")) .Verifiable(); var failure = new KnownIssue("Some failure", suggestedExitCode: (int)ExitCode.APP_CRASH); _errorKnowledgeBase .Setup(x => x.IsKnownTestIssue(It.IsAny(), out failure)) .Returns(true) .Verifiable(); var cts = new CancellationTokenSource(); _deviceFinder.Reset(); _deviceFinder .Setup(x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny())) .Callback(() => cts.Cancel()) .ReturnsAsync(new DevicePair(_simulator.Object, null)); // Act var result = await _testOrchestrator.OrchestrateTest( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: false, resetSimulator: false, enableLldb: true, signalAppEnd: false, Array.Empty<(string, string?)>(), Array.Empty(), cts.Token); // Verify Assert.Equal(ExitCode.APP_LAUNCH_TIMEOUT, result); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); } [Fact] public async Task OrchestrateTimedOutSimulatorSearchTestTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); _deviceFinder.Reset(); _deviceFinder .Setup(x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny())) .ThrowsAsync(new OperationCanceledException()); // Act var result = await _testOrchestrator.OrchestrateTest( AppPath, testTarget, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: false, resetSimulator: false, enableLldb: true, signalAppEnd: false, Array.Empty<(string, string?)>(), Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.APP_LAUNCH_TIMEOUT, result); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); } [Fact] public async Task OrchestrateFailedDeviceTestTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); var extraArguments = new[] { "--some arg1", "--some arg2" }; _appTester .Setup(x => x.TestApp( _appBundleInformation, testTarget, _device.Object, null, TimeSpan.FromMinutes(30), It.IsAny(), true, extraArguments, It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.TimedOut, SuccessResultLine)) .Verifiable(); // Act var result = await _testOrchestrator.OrchestrateTest( AppPath, testTarget, DeviceName, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: true, resetSimulator: false, enableLldb: false, signalAppEnd: true, Array.Empty<(string, string?)>(), extraArguments, new CancellationToken()); // Verify Assert.Equal(ExitCode.TIMED_OUT, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, DeviceName, It.IsAny(), true, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(_appBundleInformation, testTarget, _device.Object, It.IsAny()), Times.Once); _appTester.VerifyAll(); } [Fact] public async Task OrchestrateMacCatalystTestTest() { // Setup _appInstaller.Reset(); _appUninstaller.Reset(); _deviceFinder.Reset(); var testTarget = new TestTargetOs(TestTarget.MacCatalyst, null); var envVars = new (string, string?)[] { ("envVar1", "value1"), ("envVar2", "value2") }; _appTester .Setup(x => x.TestMacCatalystApp( _appBundleInformation, TimeSpan.FromMinutes(30), It.IsAny(), true, It.IsAny>(), envVars, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Failed, "Tests failed")) .Verifiable(); // Act var result = await _testOrchestrator.OrchestrateTest( AppPath, testTarget, null, TimeSpan.FromMinutes(30), It.IsAny(), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: false, resetSimulator: true, enableLldb: false, signalAppEnd: true, envVars, Array.Empty(), new CancellationToken()); // Verify Assert.Equal(ExitCode.TESTS_FAILED, result); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); _appTester.VerifyAll(); _deviceFinder.VerifyNoOtherCalls(); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); } [Fact] public async Task OrchestrateDeviceTestWithFailingTcpTest() { // Setup var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); var extraArguments = new[] { "--some arg1", "--some arg2" }; _appTester .Setup(x => x.TestApp( _appBundleInformation, testTarget, _device.Object, null, TimeSpan.FromMinutes(30), It.IsAny(), false, extraArguments, It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((TestExecutingResult.Crashed, "Test execution timed out")) .Verifiable(); _appTester .SetupGet(x => x.ListenerConnected) .Returns(false); KnownIssue? issue = new KnownIssue("App crashed", null, (int)ExitCode.TCP_CONNECTION_FAILED); _errorKnowledgeBase .Setup(x => x.IsKnownTestIssue(It.IsAny(), out issue)) .Returns(true); // Act var result = await _testOrchestrator.OrchestrateTest( AppPath, testTarget, DeviceName, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(3), CommunicationChannel.UsbTunnel, XmlResultJargon.xUnit, Array.Empty(), Array.Empty(), includeWirelessDevices: true, resetSimulator: false, enableLldb: false, signalAppEnd: false, Array.Empty<(string, string?)>(), extraArguments, new CancellationToken()); // Verify Assert.Equal(ExitCode.TCP_CONNECTION_FAILED, result); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Apple.Tests/Orchestration/UninstallOrchestratorTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.Apple.Tests.Orchestration; public class UninstallOrchestratorTests : OrchestratorTestBase { private readonly UninstallOrchestrator _uninstallOrchestrator; public UninstallOrchestratorTests() { _uninstallOrchestrator = new( _appBundleInformationParser.Object, _appInstaller.Object, _appUninstaller.Object, _deviceFinder.Object, _logger.Object, _logs, _mainLog.Object, _errorKnowledgeBase.Object, _diagnosticsData, _helpers.Object); } [Fact] public async Task OrchestrateSimulatorUninstallationTest() { // Setup _appUninstaller.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, })); var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); // Act var result = await _uninstallOrchestrator.OrchestrateAppUninstall( BundleIdentifier, testTarget, SimulatorName, TimeSpan.FromMinutes(30), includeWirelessDevices: false, resetSimulator: false, enableLldb: false, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, SimulatorName, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); _appUninstaller.Verify( x => x.UninstallSimulatorApp(_simulator.Object, BundleIdentifier, It.IsAny()), Times.Once); _appUninstaller.Verify( x => x.UninstallDeviceApp(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); _simulator .Verify(x => x.Boot(It.IsAny(), It.IsAny()), Times.AtLeastOnce); _simulator .Verify(x => x.GetAppBundlePath(It.IsAny(), BundleIdentifier, It.IsAny()), Times.AtLeastOnce); } [Fact] public async Task OrchestrateDeviceUninstallationTest() { // Setup _appUninstaller.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, })); var testTarget = new TestTargetOs(TestTarget.Device_tvOS, "14.2"); // Act var result = await _uninstallOrchestrator.OrchestrateAppUninstall( BundleIdentifier, testTarget, null, TimeSpan.FromMinutes(30), includeWirelessDevices: false, resetSimulator: false, enableLldb: false, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, null, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(false); VerifySimulatorCleanUp(false); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); _appUninstaller.Verify( x => x.UninstallSimulatorApp(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); _appUninstaller.Verify( x => x.UninstallDeviceApp(_device.Object, BundleIdentifier, It.IsAny()), Times.Once); } [Fact] public async Task OrchestrateSimulatorUninstallationWithResetTest() { // Setup _appUninstaller.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false, })); var testTarget = new TestTargetOs(TestTarget.Simulator_iOS64, "13.5"); // Act var result = await _uninstallOrchestrator.OrchestrateAppUninstall( BundleIdentifier, testTarget, SimulatorName, TimeSpan.FromMinutes(30), includeWirelessDevices: false, resetSimulator: true, enableLldb: false, new CancellationToken()); // Verify Assert.Equal(ExitCode.SUCCESS, result); _deviceFinder.Verify( x => x.FindDevice(testTarget, SimulatorName, It.IsAny(), false, true, It.IsAny()), Times.Once); VerifySimulatorReset(true); VerifySimulatorCleanUp(true); VerifyDiagnosticData(testTarget); _appInstaller.Verify( x => x.InstallApp(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); // Verify that when resetting the device, we don't try to uninstall unnecessarily after _appUninstaller.Verify( x => x.UninstallSimulatorApp(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); _appUninstaller.Verify( x => x.UninstallDeviceApp(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task OrchestrateMacCatalystUninstallationTest() { // Setup _appInstaller.Reset(); _appUninstaller.Reset(); _deviceFinder.Reset(); var testTarget = new TestTargetOs(TestTarget.MacCatalyst, null); // Act await _uninstallOrchestrator.OrchestrateAppUninstall( BundleIdentifier, testTarget, null, TimeSpan.FromMinutes(30), includeWirelessDevices: false, resetSimulator: false, enableLldb: false, new CancellationToken()); // Verify VerifySimulatorReset(false); VerifySimulatorCleanUp(false); _appInstaller.VerifyNoOtherCalls(); _appUninstaller.VerifyNoOtherCalls(); _deviceFinder.VerifyNoOtherCalls(); } [Fact] public async Task OrchestrateDeviceUninstallationWhenNoDeviceTest() { // Setup _deviceFinder.Reset(); _deviceFinder .Setup(x => x.FindDevice( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(new NoDeviceFoundException()); var testTarget = new TestTargetOs(TestTarget.Device_iOS, "14.2"); // Act var result = await _uninstallOrchestrator.OrchestrateAppUninstall( BundleIdentifier, testTarget, deviceName: null, TimeSpan.FromMinutes(30), includeWirelessDevices: false, resetSimulator: false, enableLldb: false, new CancellationToken()); // Verify Assert.Equal(ExitCode.DEVICE_NOT_FOUND, result); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.CLI.Tests/CommandArguments/ArgumentTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.CLI.CommandArguments; using Microsoft.DotNet.XHarness.Common.CLI; using Xunit; namespace Microsoft.DotNet.XHarness.CLI.Tests.Arguments; public class ArgumentTests { private class SampleRepeatableArgument : RepeatableArgument { public SampleRepeatableArgument() : base("a=", string.Empty) { } } [Fact] public void RepetableArgumentsAreParsed() { var arg = new SampleRepeatableArgument(); var command = UnitTestCommand.FromArgument(arg); var exitCode = command.Invoke(new[] { "-a", "foo", "-a=bar", }); Assert.Equal(0, exitCode); Assert.True(command.CommandRun); Assert.Equal(new[] { "foo", "bar" }, arg.Value); } private class SampleSwitchArgument : SwitchArgument { public SampleSwitchArgument(bool defaultValue) : base("b:", string.Empty, defaultValue) { } } [Fact] public void SwitchArgumentArgumentWithoutValueIsTrue() { var arg = new SampleSwitchArgument(false); var command = UnitTestCommand.FromArgument(arg); var exitCode = command.Invoke(new[] { "-b", }); Assert.Equal(0, exitCode); Assert.True(command.CommandRun); Assert.True(arg.Value); } [Fact] public void SwitchArgumentArgumentWithTrueDefaultIsFalse() { var arg = new SampleSwitchArgument(true); var command = UnitTestCommand.FromArgument(arg); var exitCode = command.Invoke(new[] { "-b", }); Assert.Equal(0, exitCode); Assert.True(command.CommandRun); Assert.False(arg.Value); } [Fact] public void SwitchArgumentArgumentWithValueIsFalse() { var arg = new SampleSwitchArgument(false); var command = UnitTestCommand.FromArgument(arg); var exitCode = command.Invoke(new[] { "-b=false", }); Assert.Equal(0, exitCode); Assert.True(command.CommandRun); Assert.False(arg.Value); } [Fact] public void SwitchArgumentArgumentWithDefaultValueIsFalse() { var arg = new SampleSwitchArgument(true); var command = UnitTestCommand.FromArgument(arg); var exitCode = command.Invoke(new[] { "-b=off", }); Assert.Equal(0, exitCode); Assert.True(command.CommandRun); Assert.False(arg.Value); } private class SampleStringArgument : RequiredStringArgument { public SampleStringArgument() : base("c=", string.Empty) { } } [Fact] public void RequiredStringArgumentIsSet() { var arg = new SampleStringArgument(); var command = UnitTestCommand.FromArgument(arg); var exitCode = command.Invoke(new[] { "-c", "xyz", }); Assert.Equal(0, exitCode); Assert.True(command.CommandRun); Assert.Equal("xyz", arg.Value); } [Fact] public void RequiredStringArgumentIsValidated() { var arg = new SampleStringArgument(); var command = UnitTestCommand.FromArgument(arg); var exitCode = command.Invoke(Array.Empty()); Assert.Equal((int)ExitCode.INVALID_ARGUMENTS, exitCode); } private class SampleTimeSpanArgument : TimeSpanArgument { public SampleTimeSpanArgument(TimeSpan defaultValue) : base("t=", string.Empty, defaultValue) { } } [Fact] public void TimeSpanArgumentHasDefault() { var arg = new SampleTimeSpanArgument(TimeSpan.FromMinutes(3)); var command = UnitTestCommand.FromArgument(arg); var exitCode = command.Invoke(Array.Empty()); Assert.Equal(0, exitCode); Assert.True(command.CommandRun); Assert.Equal(TimeSpan.FromMinutes(3), arg.Value); } [Fact] public void TimeSpanArgumentIsSet() { var arg = new SampleTimeSpanArgument(TimeSpan.FromMinutes(3)); var command = UnitTestCommand.FromArgument(arg); var exitCode = command.Invoke(new[] { "-t", "00:02:30", }); Assert.Equal(0, exitCode); Assert.True(command.CommandRun); Assert.Equal(TimeSpan.FromSeconds(150), arg.Value); } [Fact] public void ArgumentsAreInterpolatedWell() { var timespanArg = new SampleTimeSpanArgument(TimeSpan.FromSeconds(5)); var switchArg = new SampleSwitchArgument(true); var stringArg = new SampleStringArgument(); stringArg.Action("string-value"); Assert.Equal("time is 00:00:05", $"time is {timespanArg}"); Assert.Equal("switch is true", $"switch is {switchArg}"); Assert.Equal("string is string-value", $"string is {stringArg}"); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.CLI.Tests/Commands/XHarnessCommandTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using Microsoft.DotNet.XHarness.CLI.Android; using Microsoft.DotNet.XHarness.CLI.CommandArguments; using Microsoft.DotNet.XHarness.CLI.Commands; using Microsoft.DotNet.XHarness.CLI.Commands.Apple; using Microsoft.DotNet.XHarness.CLI.Commands.Wasm; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; using Mono.Options; using Xunit; namespace Microsoft.DotNet.XHarness.CLI.Tests.CommandArguments; public class XHarnessCommandTests { private readonly SampleUnitTestArguments _arguments; private readonly UnitTestCommand _command; public XHarnessCommandTests() { _arguments = new SampleUnitTestArguments(); _command = new UnitTestCommand(_arguments, false); } [Fact] public void ArgumentsWithEqualSignsAreParsed() { var exitCode = _command.Invoke(new[] { "--number=50", "--enum=Value2", "--string=foobar", }); Assert.Equal(0, exitCode); Assert.True(_command.CommandRun); Assert.Equal(50, _arguments.Number); Assert.Equal(SampleEnum.Value2, _arguments.Enum); Assert.Equal("foobar", _arguments.String); } [Fact] public void ArgumentsWithSpacesAreParsed() { var exitCode = _command.Invoke(new[] { "--number", "50", "--enum", "Value2", "-s", "foobar", }); Assert.Equal(0, exitCode); Assert.True(_command.CommandRun); Assert.Equal(50, _arguments.Number); Assert.Equal(SampleEnum.Value2, _arguments.Enum); Assert.Equal("foobar", _arguments.String); } [Fact] public void ArgumentsAreValidated() { var exitCode = _command.Invoke(new[] { "-n", "200", "--enum", "Value2", }); Assert.Equal((int)ExitCode.INVALID_ARGUMENTS, exitCode); Assert.False(_command.CommandRun); } [Fact] public void VerbosityArgumentIsDetected() { var exitCode = _command.Invoke(new[] { "-n", "50", "--verbosity=Warning", }); Assert.Equal(0, exitCode); Assert.True(_command.CommandRun); Assert.Equal(50, _arguments.Number); Assert.Equal(LogLevel.Warning, _arguments.Verbosity); } [Fact] public void HelpArgumentIsDetected() { var exitCode = _command.Invoke(new[] { "--help", }); Assert.Equal((int)ExitCode.HELP_SHOWN, exitCode); Assert.False(_command.CommandRun); Assert.True(_arguments.ShowHelp); } [Fact] public void ExtraneousArgumentsAreRejected() { var exitCode = _command.Invoke(new[] { "-n", "50", "--enum", "Value2", "--invalid-arg=foo", }); Assert.Equal((int)ExitCode.INVALID_ARGUMENTS, exitCode); Assert.False(_command.CommandRun); } [Fact] public void ExtraneousArgumentsAreDetected() { var arguments = new SampleUnitTestArguments(); var command = new UnitTestCommand(arguments, true); var exitCode = command.Invoke(new[] { "-n", "50", "--enum", "Value2", "some", "other=1", "args", }); Assert.Equal(0, exitCode); Assert.True(command.CommandRun); Assert.Equal(50, arguments.Number); Assert.Equal(SampleEnum.Value2, arguments.Enum); Assert.Equal(new[] { "some", "other=1", "args" }, command.ExtraArgs); } [Fact] public void EnumsAreValidated() { var exitCode = _command.Invoke(new[] { "--enum", "Foo", }); Assert.Equal((int)ExitCode.INVALID_ARGUMENTS, exitCode); Assert.False(_command.CommandRun); } [Fact] public void ForbiddenEnumValuesAreValidated() { var exitCode = _command.Invoke(new[] { "--enum", "ForbiddenValue", }); Assert.Equal((int)ExitCode.INVALID_ARGUMENTS, exitCode); Assert.False(_command.CommandRun); } [Fact] public void PassThroughArgumentsAreParsed() { var exitCode = _command.Invoke(new[] { "-n", "50", "--enum", "Value2", Program.VerbatimArgumentPlaceholder, "v8", "--foo", "runtime.js", }); Assert.Equal(0, exitCode); Assert.True(_command.CommandRun); Assert.Equal(50, _arguments.Number); Assert.Equal(SampleEnum.Value2, _arguments.Enum); Assert.Equal(new[] { "v8", "--foo", "runtime.js" }, _command.PassThroughArgs.ToArray()); } [Fact] public void PassThroughArgumentsAreParsedInCommandSet() { var arguments = new SampleUnitTestArguments(); var command = new UnitTestCommand(arguments, false); var commandSet = new CommandSet("set") { command }; var exitCode = commandSet.Run(new[] { "unit-test", "-n", "50", "--enum", "Value2", Program.VerbatimArgumentPlaceholder, "v8", "--foo", "runtime.js", }); Assert.Equal(0, exitCode); Assert.True(command.CommandRun); Assert.Equal(50, arguments.Number); Assert.Equal(SampleEnum.Value2, arguments.Enum); Assert.Equal(new[] { "v8", "--foo", "runtime.js" }, command.PassThroughArgs.ToArray()); } [Fact] public void ExtraneousArgumentsDetectedInPassThroughMode() { var exitCode = _command.Invoke(new[] { "v8", "--foo", "runtime.js", Program.VerbatimArgumentPlaceholder, "-n", "50", "--enum", "--invalid-arg=foo", }); Assert.Equal((int)ExitCode.INVALID_ARGUMENTS, exitCode); Assert.False(_command.CommandRun); } [Theory] [InlineData("apple test -h")] [InlineData("apple run -h")] [InlineData("apple just-test -h")] [InlineData("apple just-run -h")] [InlineData("apple install -h")] [InlineData("apple uninstall -h")] [InlineData("apple state -h")] [InlineData("android test -h")] [InlineData("android run -h")] [InlineData("android install -h")] [InlineData("android uninstall -h")] [InlineData("android state -h")] [InlineData("wasm test -h")] [InlineData("wasm test-browser -h")] public void ArgumentPrototypesAreNotClashing(string command) { // This test tries to run all of the commands which will fail because of some missing argument // If we for example add a new option and that would clash with some already existing argument, // this will fail to add the duplicate option prototype. // (it already happened that we added -d to all commands and the WASM command failed because it already had -d) var commandSet = new CommandSet("test") { new AppleCommandSet(), new AndroidCommandSet(), new WasmCommandSet(), new XHarnessHelpCommand(), new XHarnessVersionCommand() }; Assert.Equal((int)ExitCode.HELP_SHOWN, commandSet.Run(command.Split(" "))); } private class SampleUnitTestArguments : XHarnessCommandArguments { public SampleNumberArgument Number { get; } = new(); public SampleEnumArgument Enum { get; } = new(); public SampleStringArgument String { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { Number, Enum, String, }; } private enum SampleEnum { Value1, Value2, ForbiddenValue, } private class SampleNumberArgument : IntArgument { public SampleNumberArgument() : base("number=|n=", "Sets the number, should be less than 100") { } public override void Validate() { base.Validate(); if (Value > 100) { throw new ArgumentOutOfRangeException(); } } } private class SampleEnumArgument : Argument { public SampleEnumArgument() : base("enum=|e=", "Sets the enum", SampleEnum.Value1) { } public override void Action(string argumentValue) => Value = ParseArgument("enum", argumentValue, SampleEnum.ForbiddenValue); } private class SampleStringArgument : StringArgument { public SampleStringArgument() : base("string=|s=", "Sets the string") { } } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.CLI.Tests/Microsoft.DotNet.XHarness.CLI.Tests.csproj ================================================ $(NetCurrent) $(NoWarn);CS8002 ================================================ FILE: tests/Microsoft.DotNet.XHarness.CLI.Tests/Resources/StringsTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.CLI.Resources; using Xunit; namespace Microsoft.DotNet.XHarness.CLI.Tests.Resources; public class StringsTests { [Fact] public void ResourcesCanBeLoaded() { Assert.NotNull(Strings.Apple_Test_Description); Assert.NotEmpty(Strings.Apple_Test_Description); Assert.NotNull(Strings.Apple_Test_Usage); Assert.NotEmpty(Strings.Apple_Test_Usage); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.CLI.Tests/UnitTestArguments.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using Microsoft.DotNet.XHarness.CLI.CommandArguments; namespace Microsoft.DotNet.XHarness.CLI.Tests; internal class UnitTestArguments : XHarnessCommandArguments where TArgument : Argument { public UnitTestArguments(TArgument argument) { Argument = argument; } public TArgument Argument { get; } protected override IEnumerable GetArguments() => new[] { Argument }; } ================================================ FILE: tests/Microsoft.DotNet.XHarness.CLI.Tests/UnitTestCommand.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.CLI.CommandArguments; using Microsoft.DotNet.XHarness.CLI.Commands; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.XHarness.CLI.Tests; internal class UnitTestCommand { public static UnitTestCommand> FromArgument(TArgument arg) where TArgument : Argument { return new UnitTestCommand>(new UnitTestArguments(arg)); } } internal class UnitTestCommand : XHarnessCommand where TArguments : XHarnessCommandArguments { protected override string CommandUsage => "test"; protected override string CommandDescription => "unit test command"; public bool CommandRun { get; private set; } public IEnumerable PassThroughArgs => PassThroughArguments; public IEnumerable ExtraArgs => ExtraArguments; private readonly TArguments _arguments; protected override TArguments Arguments => _arguments; public UnitTestCommand(TArguments arguments, bool allowExtraArgs = false) : base(TargetPlatform.Apple, "unit-test", allowExtraArgs, new ServiceCollection()) { _arguments = arguments; } protected override Task InvokeInternal(ILogger logger) { CommandRun = true; return Task.FromResult(ExitCode.SUCCESS); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Common.Tests/Execution/ProcessManagerTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Xunit; namespace Microsoft.DotNet.XHarness.Common.Tests.Execution; public class ProcessManagerTests { [Fact(Skip = "ping is not available in AzDO so this is rather for local development")] public async Task ProcessShouldBeKilled() { var pm = ProcessManagerFactory.CreateProcessManager(); var process = new Process(); process.StartInfo.FileName = "ping"; process.StartInfo.Arguments = "-t 127.0.0.1"; var log = new MemoryLog(); var result = await pm.RunAsync(process, log, TimeSpan.FromSeconds(3)); Assert.True(result.TimedOut); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Common.Tests/Logging/CallbackLogTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using Microsoft.DotNet.XHarness.Common.Logging; using Xunit; namespace Microsoft.DotNet.XHarness.Common.Tests.Logging; public class CallbackLogTest { [Fact] public void OnWriteTest() { var message = "This is a log message"; bool called = false; string data = null; Action cb = (d) => { called = true; data = d; }; var log = new CallbackLog(cb); log.Write(message); Assert.True(called, "Callback was not called"); Assert.NotNull(data); Assert.EndsWith(message, data); // TODO: take time stamp into account } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Common.Tests/Logging/ConsoleLogTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using Microsoft.DotNet.XHarness.Common.Logging; using Xunit; namespace Microsoft.DotNet.XHarness.Common.Tests.Logging; [CollectionDefinition("ConsoleLogTest", DisableParallelization = true)] public class ConsoleLogTest : IDisposable { private readonly string _testFile; private readonly TextWriter _sdoutWriter; private readonly ConsoleLog _log; public ConsoleLogTest() { _testFile = Path.GetTempFileName(); _log = new ConsoleLog(); _sdoutWriter = Console.Out; } [Fact(Skip = "Flakey test that gets in the way by messing around with Console.Out")] public void TestWrite() { var message = "This is a log message"; using (var testStream = new FileStream(_testFile, FileMode.OpenOrCreate, FileAccess.Write)) using (var writer = new StreamWriter(testStream)) { Console.SetOut(writer); // simply test that we do write in the file. We need to close the stream to be able to read it _log.WriteLine(message); } using (var testStream = new FileStream(_testFile, FileMode.OpenOrCreate, FileAccess.Read)) using (var reader = new StreamReader(testStream)) { var line = reader.ReadLine(); Assert.EndsWith(message, line); // consider the time stamp } } public void Dispose() { _log.Dispose(); Console.SetOut(_sdoutWriter); // get back to write to the console File.Delete(_testFile); GC.SuppressFinalize(this); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Common.Tests/Logging/ScanLogTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Common.Logging; using Xunit; namespace Microsoft.DotNet.XHarness.Common.Tests.Logging; public class ScanLogTest { [Theory] [InlineData("This is a log message", "log", true)] [InlineData("emessag", "message", false)] [InlineData("This is a log message", "This is a log message", true)] [InlineData("This is a log message.", ".", true)] public void TagIsFoundInLog(string message, string tag, bool shouldFind) { bool found = false; var log = new ScanLog(tag, () => found = true); log.Write(message); Assert.Equal(shouldFind, found); } [Fact] public void TagIsFoundInSeveralMessages() { bool found = false; var log = new ScanLog("123", () => found = true); log.Write("abc1"); log.Write("2"); log.Write("3cdef"); Assert.True(found); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.Common.Tests/Microsoft.DotNet.XHarness.Common.Tests.csproj ================================================ $(NetCurrent) $(NoWarn);CS8002 disable ================================================ FILE: tests/Microsoft.DotNet.XHarness.Common.Tests/Utilities/StringUtilsTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using Microsoft.DotNet.XHarness.Common.Utilities; using Xunit; namespace Microsoft.DotNet.XHarness.Common.Tests.Utilities; public class StringUtilsTests { private static readonly char s_shellQuoteChar = (int)Environment.OSVersion.Platform != 128 && Environment.OSVersion.Platform != PlatformID.Unix && Environment.OSVersion.Platform != PlatformID.MacOSX ? '"' // Windows : '\''; // !Windows [Fact] public void NoEscapingNeeded() => Assert.Equal("foo", StringUtils.Quote("foo")); [Theory] [InlineData("foo bar", "foo bar")] [InlineData("foo \"bar\"", "foo \\\"bar\\\"")] [InlineData("foo bar's", "foo bar\\\'s")] [InlineData("foo $bar's", "foo $bar\\\'s")] public void QuoteForProcessTest(string input, string expected) => Assert.Equal(s_shellQuoteChar + expected + s_shellQuoteChar, StringUtils.Quote(input)); [Fact(Skip = "Only works on OSX/Linux")] public void FormatArgumentsTest() { var p = new Process(); p.StartInfo.RedirectStandardOutput = true; p.StartInfo.UseShellExecute = false; p.StartInfo.FileName = "/bin/echo"; var complexInput = "'"; p.StartInfo.Arguments = StringUtils.FormatArguments("-n", "foo", complexInput, "bar"); p.Start(); var output = p.StandardOutput.ReadToEnd(); Assert.Equal($"foo {complexInput} bar", output); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.TestRunners.Tests/CoverageManagerTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using Microsoft.DotNet.XHarness.TestRunners.Common; using Xunit; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Tests; public class CoverageManagerTests : IDisposable { private readonly string _tempDir; public CoverageManagerTests() { _tempDir = Path.Combine(Path.GetTempPath(), "xharness-coverage-test-" + Guid.NewGuid().ToString("N")[..8]); Directory.CreateDirectory(_tempDir); } public void Dispose() { if (Directory.Exists(_tempDir)) { Directory.Delete(_tempDir, recursive: true); } } [Fact] public void Constructor_UsesProvidedPath() { var path = Path.Combine(_tempDir, "my-coverage.xml"); var manager = new CoverageManager(path); Assert.Equal(path, manager.OutputPath); } [Fact] public void Constructor_DefaultsToTempPath_WhenNull() { var manager = new CoverageManager(null); Assert.Contains("coverage.cobertura.xml", manager.OutputPath); } [Fact] public void PrepareForCoverage_CreatesOutputDirectory() { var subDir = Path.Combine(_tempDir, "nested", "output"); var outputPath = Path.Combine(subDir, "coverage.xml"); var manager = new CoverageManager(outputPath); manager.PrepareForCoverage(); Assert.True(Directory.Exists(subDir)); } [Fact] public void PrepareForCoverage_SetsEnvironmentVariable() { var outputPath = Path.Combine(_tempDir, "coverage.xml"); var manager = new CoverageManager(outputPath); manager.PrepareForCoverage(); Assert.Equal(outputPath, Environment.GetEnvironmentVariable("COVERAGE_OUTPUT_PATH")); } [Fact] public void GetCoverageResults_ReturnsPath_WhenFileExists() { var outputPath = Path.Combine(_tempDir, "coverage.cobertura.xml"); File.WriteAllText(outputPath, ""); var manager = new CoverageManager(outputPath); var result = manager.GetCoverageResults(); Assert.Equal(outputPath, result); } [Fact] public void GetCoverageResults_FindsCoberturaFile_InSameDirectory() { File.WriteAllText(Path.Combine(_tempDir, "coverage.cobertura.xml"), ""); var outputPath = Path.Combine(_tempDir, "other-name.xml"); var manager = new CoverageManager(outputPath); var result = manager.GetCoverageResults(); Assert.NotNull(result); Assert.EndsWith("coverage.cobertura.xml", result); } [Fact] public void GetCoverageResults_ReturnsNull_WhenNoFileExists() { var outputPath = Path.Combine(_tempDir, "nonexistent.xml"); var manager = new CoverageManager(outputPath); var result = manager.GetCoverageResults(); Assert.Null(result); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.TestRunners.Tests/Microsoft.DotNet.XHarness.TestRunners.Tests.csproj ================================================ $(NetCurrent) false disable ================================================ FILE: tests/Microsoft.DotNet.XHarness.TestRunners.Tests/NUnit/NUnit3XmlOutputWriterTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml; using Microsoft.DotNet.XHarness.TestRunners.NUnit; using Moq; using NUnit.Engine; using Xunit; namespace Microsoft.DotNet.XHarness.TestRunners.Tests.NUnit; public class NUnit3XmlOutputWriterTests : IDisposable { private const string SampleFileName = "NUnitV3Sample.xml"; private readonly Mock _resultSummary; private readonly string _tempPath; public NUnit3XmlOutputWriterTests() { _resultSummary = new Mock(); _tempPath = Path.GetTempFileName(); File.Delete(_tempPath); } private XmlNode GetTestRunSample() { var resourcenames = GetType().Assembly.GetManifestResourceNames(); // load the test-run node from the sample file var name = GetType().Assembly .GetManifestResourceNames().FirstOrDefault(a => a.EndsWith(SampleFileName, StringComparison.Ordinal)); var doc = new XmlDocument(); using var sampleStream = new StreamReader(GetType().Assembly.GetManifestResourceStream(name)); doc.Load(sampleStream); return doc.SelectNodes("test-run")[0]; } [Fact] public void SingleTestRunTest() { var testRun = new Mock(); testRun.Setup(t => t.Result).Returns(GetTestRunSample()); // set the expectations of the mock, the important thing, we want to return a single test-run node _resultSummary.Setup(rs => rs.GetEnumerator()) .Returns(new List { testRun.Object }.GetEnumerator()); using (var writer = new StreamWriter(_tempPath)) { var nunit3Writer = new NUnit3XmlOutputWriter(DateTime.Now); nunit3Writer.WriteResultFile(_resultSummary.Object, writer); } // read the file and make sure that is correct var doc = new XmlDocument(); doc.Load(_tempPath); // we just need to make sure we have a single test-run node and a single env node, the rest // was generated by nunit var runs = doc.SelectNodes(".//test-run"); Assert.Equal(1, runs.Count); var enviroment = doc.SelectNodes(".//environment"); Assert.Equal(1, enviroment.Count); } [Fact] public void SeveralTetRunTest() { // same logic as with the other tests, but with more than one test run var firstTestRun = new Mock(); firstTestRun.Setup(t => t.Result).Returns(GetTestRunSample()); var secondTestRun = new Mock(); secondTestRun.Setup(t => t.Result).Returns(GetTestRunSample()); _resultSummary.Setup(rs => rs.GetEnumerator()) .Returns(new List { firstTestRun.Object, secondTestRun.Object }.GetEnumerator()); using (var writer = new StreamWriter(_tempPath)) { var nunit3Writer = new NUnit3XmlOutputWriter(DateTime.Now); nunit3Writer.WriteResultFile(_resultSummary.Object, writer); } // read the file and make sure that is correct var doc = new XmlDocument(); doc.Load(_tempPath); var runs = doc.SelectNodes(".//test-run"); Assert.Equal(1, runs.Count); var enviroment = doc.SelectNodes(".//environment"); Assert.Equal(1, enviroment.Count); } public void Dispose() { File.Delete(_tempPath); GC.SuppressFinalize(this); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.TestRunners.Tests/NUnit/TestStatusExtensionsTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.TestRunners.NUnit; using NUnit.Framework.Interfaces; using Xunit; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Tests.NUnit; public class TestStatusExtensionsTests { public class TestStatusExtensionsTestData { public static IEnumerable ToXmlResultValueTests { get { // NUnit v2 yield return new object[] { TestStatus.Failed, XmlResultJargon.NUnitV2, "Failure", }; yield return new object[] { TestStatus.Inconclusive, XmlResultJargon.NUnitV2, "Inconclusive", }; yield return new object[] { TestStatus.Passed, XmlResultJargon.NUnitV2, "Success" }; yield return new object[] { TestStatus.Skipped, XmlResultJargon.NUnitV2, "Ignored" }; yield return new object[] { TestStatus.Warning, XmlResultJargon.NUnitV2, "Failure" }; // NUnit v3 yield return new object[] { TestStatus.Failed, XmlResultJargon.NUnitV3, "Failed", }; yield return new object[] { TestStatus.Inconclusive, XmlResultJargon.NUnitV3, "Inconclusive", }; yield return new object[] { TestStatus.Passed, XmlResultJargon.NUnitV3, "Passed" }; yield return new object[] { TestStatus.Skipped, XmlResultJargon.NUnitV3, "Skipped" }; yield return new object[] { TestStatus.Warning, XmlResultJargon.NUnitV3, "Failed" }; // xunit yield return new object[] { TestStatus.Failed, XmlResultJargon.xUnit, "Fail", }; yield return new object[] { TestStatus.Inconclusive, XmlResultJargon.xUnit, "Skip", }; yield return new object[] { TestStatus.Passed, XmlResultJargon.xUnit, "Pass" }; yield return new object[] { TestStatus.Skipped, XmlResultJargon.xUnit, "Skip" }; yield return new object[] { TestStatus.Warning, XmlResultJargon.xUnit, "Fail" }; } } [Theory] [MemberData(nameof(ToXmlResultValueTests), MemberType = typeof(TestStatusExtensionsTestData))] public void IsExcludedAsAssembly(TestStatus status, XmlResultJargon jargon, string expectedResult) => Assert.Equal(status.ToXmlResultValue(jargon), expectedResult); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.TestRunners.Tests/xUnit/XUnitFilterTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Reflection; using System.Text; using Microsoft.DotNet.XHarness.TestRunners.Common; using Microsoft.DotNet.XHarness.TestRunners.Xunit; using Moq; using Xunit; using Xunit.Abstractions; #nullable enable namespace Microsoft.DotNet.XHarness.TestRunners.Tests.xUnit; public class XUnitFilterTests { public class FiltersTestData { public static IEnumerable TraitFilters { get { const string traitName = "MyTrait"; const string traitValue = "MyValue"; // no traits, should not be excluded var filter = XUnitFilter.CreateTraitFilter( traitName: traitName, traitValue: null, exclude: true); var testCase = new Mock(); var method = new Mock(); testCase.Setup(t => t.Traits).Returns(new Dictionary>()); testCase.Setup(t => t.TestMethod).Returns(method.Object); method.Setup(m => m.Method).Returns((IMethodInfo)null!); yield return new object[] { filter, testCase.Object, false, // test is not exclude since we are exclude and have no traits "", }; // no traits, included filter = XUnitFilter.CreateTraitFilter( traitName: traitName, traitValue: null, exclude: false); testCase = new Mock(); testCase.Setup(t => t.Traits).Returns(new Dictionary>()); testCase.Setup(t => t.TestMethod).Returns(method.Object); method.Setup(m => m.Method).Returns((IMethodInfo)null!); yield return new object[] { filter, testCase.Object, true, // no traits and filter is included, means we exclude "[FILTER] Excluded test", }; // trait present, no value, exclude filter = XUnitFilter.CreateTraitFilter( traitName: traitName, traitValue: null, exclude: true); testCase = new Mock(); testCase.Setup(t => t.Traits).Returns(new Dictionary> { [traitName] = null!, }); testCase.Setup(t => t.TestMethod).Returns(method.Object); method.Setup(m => m.Method).Returns((IMethodInfo)null!); yield return new object[] { filter, testCase.Object, true, // have the trait, no values and no values in the filter "[FILTER] Excluded test", }; // trait present, no value, include filter = XUnitFilter.CreateTraitFilter( traitName: traitName, traitValue: null, exclude: false); testCase = new Mock(); testCase.Setup(t => t.Traits).Returns(new Dictionary> { [traitName] = null!, }); testCase.Setup(t => t.TestMethod).Returns(method.Object); method.Setup(m => m.Method).Returns((IMethodInfo)null!); yield return new object[] { filter, testCase.Object, false, // have the trait and we are including "[FILTER] Included test", }; // trait present, preset value, exclude filter = XUnitFilter.CreateTraitFilter( traitName: traitName, traitValue: traitValue, exclude: true); testCase = new Mock(); testCase.Setup(t => t.Traits).Returns(new Dictionary> { [traitName] = new List { traitValue }, }); testCase.Setup(t => t.TestMethod).Returns(method.Object); method.Setup(m => m.Method).Returns((IMethodInfo)null!); yield return new object[] { filter, testCase.Object, true, // exclude, got the trait and value "[FILTER] Excluded test", }; // trait present, present value, include filter = XUnitFilter.CreateTraitFilter( traitName: traitName, traitValue: traitValue, exclude: false); testCase = new Mock(); testCase.Setup(t => t.Traits).Returns(new Dictionary> { [traitName] = new List { traitValue }, }); testCase.Setup(t => t.TestMethod).Returns(method.Object); method.Setup(m => m.Method).Returns((IMethodInfo)null!); yield return new object[] { filter, testCase.Object, false, // we are including "[FILTER] Included test", }; // trait present, missing value, exclude filter = XUnitFilter.CreateTraitFilter( traitName: traitName, traitValue: traitValue, exclude: true); testCase = new Mock(); testCase.Setup(t => t.Traits).Returns(new Dictionary> { [traitName] = new List { new string('$', 4) }, }); testCase.Setup(t => t.TestMethod).Returns(method.Object); method.Setup(m => m.Method).Returns((IMethodInfo)null!); yield return new object[] { filter, testCase.Object, false, // not excluded, we have the trait, not the value "[FILTER] Included test", }; // trait present, missing value, include filter = XUnitFilter.CreateTraitFilter( traitName: traitName, traitValue: traitValue, exclude: false); testCase = new Mock(); testCase.Setup(t => t.Traits).Returns(new Dictionary> { [traitName] = new List { new string('$', 4) }, }); testCase.Setup(t => t.TestMethod).Returns(method.Object); method.Setup(m => m.Method).Returns((IMethodInfo)null!); yield return new object[] { filter, testCase.Object, true, // we are including, but do not have the correct value "[FILTER] Excluded test", }; } } public static IEnumerable TypeNameFilters { get { var testClass = "MyClass"; // null/empty class, means the opposite, is like a no match var filter = XUnitFilter.CreateClassFilter(className: testClass, exclude: true); var testMethod = new Mock(); var testCase = new Mock(); testCase.Setup(t => t.TestMethod).Returns(testMethod.Object); testMethod.Setup(t => t.TestClass).Returns((ITestClass)null!); yield return new object[] { filter, testCase.Object, false, "", }; // not null test name no match, excluded filter = XUnitFilter.CreateClassFilter(className: testClass, exclude: true); testMethod = new Mock(); testCase = new Mock(); testCase.Setup(t => t.TestMethod).Returns(testMethod.Object); testMethod.Setup(t => t.TestClass.Class.Name).Returns("OtherClass"); yield return new object[] { filter, testCase.Object, false, // test has to be included "", }; // not null name match, exclude filter = XUnitFilter.CreateClassFilter(className: testClass, exclude: true); testMethod = new Mock(); testCase = new Mock(); testCase.Setup(t => t.TestMethod).Returns(testMethod.Object); testMethod.Setup(t => t.TestClass.Class.Name).Returns(testClass); yield return new object[] { filter, testCase.Object, true, // exclude "[FILTER] Excluded test", }; // not null name match, include filter = XUnitFilter.CreateClassFilter(className: testClass, exclude: false); testMethod = new Mock(); testCase = new Mock(); testCase.Setup(t => t.TestMethod).Returns(testMethod.Object); testMethod.Setup(t => t.TestClass.Class.Name).Returns(testClass); yield return new object[] { filter, testCase.Object, false, // include "[FILTER] Included test", }; } } public static IEnumerable SingleFilters { get { var testDisplayName = "MyNameSpace.MyClassTest.TestThatFooEqualsBat"; // match and exclude var filter = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: true); var testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); yield return new object[] { filter, testCase.Object, true, // we do exclude "[FILTER] Excluded test", }; // match an include filter = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: false); testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); yield return new object[] { filter, testCase.Object, false, // we do include "[FILTER] Included test", }; // not match, exclude, therefore include filter = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: true); testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns("OtherTest"); yield return new object[] { filter, testCase.Object, false, // we do include "[FILTER] Included test", }; // not match, include, therefore exclude filter = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: false); testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns("OtherTest"); yield return new object[] { filter, testCase.Object, true, // we do exclude "[FILTER] Excluded test", }; } } public static IEnumerable NamespaceFilters { get { var testNamespace = "MyNameSpace"; // null and exclude, therefore include var filter = XUnitFilter.CreateNamespaceFilter( namespaceName: testNamespace, exclude: true); var testCase = new Mock(); testCase.Setup(t => t.TestMethod.TestClass.Class.Name).Returns((string)null!); yield return new object[] { filter, testCase.Object, false, // we do include "[FILTER] Included test", }; // null and include, therefore exclude filter = XUnitFilter.CreateNamespaceFilter( namespaceName: testNamespace, exclude: false); testCase = new Mock(); testCase.Setup(t => t.TestMethod.TestClass.Class.Name).Returns((string)null!); yield return new object[] { filter, testCase.Object, true, // we do include "[FILTER] Excluded test", }; // match and exclude filter = XUnitFilter.CreateNamespaceFilter( namespaceName: testNamespace, exclude: false); testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testNamespace); testCase.Setup(t => t.TestMethod.TestClass.Class.Name).Returns($"{testCase}.MyClass"); yield return new object[] { filter, testCase.Object, true, // we do exclude "[FILTER] Excluded test", }; // match and include filter = XUnitFilter.CreateNamespaceFilter( namespaceName: testNamespace, exclude: true); testCase = new Mock(); testCase.Setup(t => t.TestMethod.TestClass.Class.Name).Returns($"{testCase}.MyClass"); yield return new object[] { filter, testCase.Object, false, // we do include "[FILTER] Included test", }; // no match and exclude, therefore include filter = XUnitFilter.CreateNamespaceFilter( namespaceName: testNamespace, exclude: true); testCase = new Mock(); testCase.Setup(t => t.TestMethod.TestClass.Class.Name).Returns("OtherNamespace.MyClass"); yield return new object[] { filter, testCase.Object, false, // we do include "[FILTER] Included test", }; // no match and include, therefore exclude filter = XUnitFilter.CreateNamespaceFilter( namespaceName: testNamespace, exclude: false); testCase = new Mock(); testCase.Setup(t => t.TestMethod.TestClass.Class.Name).Returns("OtherNamespace.MyClass"); yield return new object[] { filter, testCase.Object, true, // we do exclude "[FILTER] Excluded test", }; } } public static IEnumerable AssemblyFilters { get { var currentAssembly = Assembly.GetExecutingAssembly(); var assemblyName = $"{currentAssembly.GetName().Name}.dll"; var assemblyPath = currentAssembly.Location; var assemblyInfo = new TestAssemblyInfo(currentAssembly, assemblyPath); // assembly name match, exclude var filter = XUnitFilter.CreateAssemblyFilter(assemblyName: assemblyName!, exclude: true); yield return new object[] { filter, assemblyInfo, true, // exclude "[FILTER] Excluded assembly", }; // assembly name match, include filter = XUnitFilter.CreateAssemblyFilter(assemblyName: assemblyName, exclude: false); yield return new object[] { filter, assemblyInfo, false, // include "[FILTER] Included assembly", }; // assembly name no match, exclude filter = XUnitFilter.CreateAssemblyFilter(assemblyName: "OtherAssembly.dll", exclude: true); yield return new object[] { filter, assemblyInfo, false, // include "[FILTER] Included assembly", }; // assembly name no match, include filter = XUnitFilter.CreateAssemblyFilter(assemblyName: "OtherAssembly.dll", exclude: false); yield return new object[] { filter, assemblyInfo, true, // exclude "[FILTER] Excluded assembly", }; } } [Theory] [MemberData(nameof(TraitFilters), MemberType = typeof(FiltersTestData))] [MemberData(nameof(TypeNameFilters), MemberType = typeof(FiltersTestData))] [MemberData(nameof(SingleFilters), MemberType = typeof(FiltersTestData))] [MemberData(nameof(NamespaceFilters), MemberType = typeof(FiltersTestData))] internal void ApplyFilters(XUnitFilter filter, ITestCase testCase, bool excluded, string logMessage) { var logOutut = new StringBuilder(); Action? log = (s) => { logOutut.AppendLine(s); }; var testExcluded = filter.IsExcluded(testCase, log); Assert.Equal(excluded, testExcluded); // validate with the log Assert.StartsWith(logMessage, logOutut.ToString().Trim()); } [Theory] [MemberData(nameof(AssemblyFilters), MemberType = typeof(FiltersTestData))] internal void ApplyAssemblyFilter(XUnitFilter filter, TestAssemblyInfo info, bool excluded, string logMessage) { var logOutut = new StringBuilder(); Action? log = (s) => { logOutut.AppendLine(s); }; var testExcluded = filter.IsExcluded(info, log); Assert.Equal(excluded, testExcluded); // validate with the log Assert.StartsWith(logMessage, logOutut.ToString().Trim()); } } [Fact] public void CreateSingleFilterNullTestName() { Assert.Throws(() => XUnitFilter.CreateSingleFilter(null!, true)); Assert.Throws(() => XUnitFilter.CreateSingleFilter("", true)); } [Theory] [InlineData("TestMethod", "TestAssembly", true)] [InlineData("TestMethod", "TestAssembly", false)] [InlineData("TestMethod", null, false)] public void CreateSingleFilter(string methodName, string? assemblyName, bool excluded) { var filter = XUnitFilter.CreateSingleFilter(methodName, excluded, assemblyName); Assert.Equal(methodName, filter.SelectorValue); Assert.Equal(assemblyName, filter.AssemblyName); Assert.Equal(excluded, filter.Exclude); Assert.Equal(XUnitFilterType.Single, filter.FilterType); } [Fact] public void CreateAssemblyFilterNullAssemblyName() { Assert.Throws(() => XUnitFilter.CreateAssemblyFilter(null!, true)); Assert.Throws(() => XUnitFilter.CreateAssemblyFilter("", true)); } [Theory] [InlineData("MyTestAssembly.exe", true)] [InlineData("MySecondAssembly.dll", true)] [InlineData("MyTestAssembly.dll", false)] public void CreateAssemblyFilter(string assemblyName, bool excluded) { var filter = XUnitFilter.CreateAssemblyFilter(assemblyName, excluded); Assert.Null(filter.SelectorName); Assert.Equal(assemblyName, filter.AssemblyName); Assert.Equal(excluded, filter.Exclude); Assert.Equal(XUnitFilterType.Assembly, filter.FilterType); } [Fact] public void CreateAssemblyFilterMissingExtension() => Assert.Throws(() => XUnitFilter.CreateAssemblyFilter("MissinExtension", true)); [Fact] public void CreateNamespaceFilterNullNameSpace() { Assert.Throws(() => XUnitFilter.CreateNamespaceFilter(null!, true)); Assert.Throws(() => XUnitFilter.CreateNamespaceFilter("", true)); } [Theory] [InlineData("MyNameSpace", "MyAssembly", true)] [InlineData("MyNameSpace", "MyAssembly", false)] [InlineData("MyNameSpace", null, false)] public void CreateNamespaceFilter(string nameSpace, string? assemblyName, bool excluded) { var filter = XUnitFilter.CreateNamespaceFilter(nameSpace, excluded, assemblyName); Assert.Equal(nameSpace, filter.SelectorValue); Assert.Equal(assemblyName, filter.AssemblyName); Assert.Equal(excluded, filter.Exclude); Assert.Equal(XUnitFilterType.Namespace, filter.FilterType); } [Fact] public void CreateClassFilterNullClassName() { Assert.Throws(() => XUnitFilter.CreateClassFilter(null!, true)); Assert.Throws(() => XUnitFilter.CreateClassFilter("", true)); } [Theory] [InlineData("MyClass", "MyAssembly", true)] [InlineData("MyClass", "MyAssembly", false)] [InlineData("MyClass", null, false)] public void CreateClassFilter(string className, string? assemblyName, bool excluded) { var filter = XUnitFilter.CreateClassFilter(className, excluded, assemblyName); Assert.Equal(className, filter.SelectorValue); Assert.Equal(assemblyName, filter.AssemblyName); Assert.Equal(excluded, filter.Exclude); Assert.Equal(XUnitFilterType.TypeName, filter.FilterType); } [Fact] public void CreateTraitFilterNullTrait() { Assert.Throws(() => XUnitFilter.CreateTraitFilter(null!, "value", true)); Assert.Throws(() => XUnitFilter.CreateTraitFilter("", "value", true)); } [Theory] [InlineData("MyTrait", "MyTraitValue", true)] [InlineData("MyTrait", "MyTraitValue", false)] [InlineData("MyTrait", null, false)] public void CreateTraitFilter(string trait, string? traitValue, bool excluded) { var filter = XUnitFilter.CreateTraitFilter(trait, traitValue, excluded); Assert.Equal(trait, filter.SelectorName); if (traitValue == null) { Assert.Equal(string.Empty, filter.SelectorValue); } else { Assert.Equal(traitValue, filter.SelectorValue); } Assert.Null(filter.AssemblyName); Assert.Equal(excluded, filter.Exclude); Assert.Equal(XUnitFilterType.Trait, filter.FilterType); } [Theory] [InlineData(XUnitFilterType.Namespace)] [InlineData(XUnitFilterType.Single)] [InlineData(XUnitFilterType.Trait)] [InlineData(XUnitFilterType.TypeName)] internal void ApplyWrongTypeToAssembly(XUnitFilterType type) { // build and assembly for the given type XUnitFilter? filter = null; switch (type) { case XUnitFilterType.Namespace: filter = XUnitFilter.CreateNamespaceFilter("foo", true); break; case XUnitFilterType.Single: filter = XUnitFilter.CreateSingleFilter("foo", true); break; case XUnitFilterType.Trait: filter = XUnitFilter.CreateTraitFilter("foo", null, true); break; case XUnitFilterType.TypeName: filter = XUnitFilter.CreateClassFilter("foo", true); break; default: Assert.Fail("Unexpected filter type"); break; } var assebly = new TestAssemblyInfo(Assembly.GetAssembly(typeof(XUnitFilterType)), "path"); Assert.Throws(() => filter?.IsExcluded(assebly)); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.TestRunners.Tests/xUnit/XUnitFiltersCollectionTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.DotNet.XHarness.TestRunners.Common; using Microsoft.DotNet.XHarness.TestRunners.Xunit; using Moq; using Xunit; using Xunit.Abstractions; namespace Microsoft.DotNet.XHarness.TestRunners.Tests.xUnit; public class XUnitFiltersCollectionTests { public class FiltersTestData { public static IEnumerable TestCaseFilters { get { var testDisplayName = "MyNameSpace.MyClassTest.TestThatFooEqualsBat"; // no filters, should include var collection = new XUnitFiltersCollection { }; var testCase = new Mock(); yield return new object[] { collection, testCase.Object, false, }; // single filter that excludes // match and exclude var filter = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: true); collection = new XUnitFiltersCollection { filter }; testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); yield return new object[] { collection, testCase.Object, true, }; // single filter that includes the test case in the run filter = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: false); collection = new XUnitFiltersCollection { filter }; testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); yield return new object[] { collection, testCase.Object, false, }; // one excluding filter, no match, should include collection = new XUnitFiltersCollection { }; filter = XUnitFilter.CreateSingleFilter( singleTestName: $"not_{testDisplayName}", exclude: true); collection = new XUnitFiltersCollection { filter }; testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); yield return new object[] { collection, testCase.Object, false, }; // one including filter, no match, should exclude collection = new XUnitFiltersCollection { }; filter = XUnitFilter.CreateSingleFilter( singleTestName: $"not_{testDisplayName}", exclude: false); collection = new XUnitFiltersCollection { filter }; testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); yield return new object[] { collection, testCase.Object, true, }; // two excluding filters, match both, should exclude filter = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: true); var filter2 = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: true); collection = new XUnitFiltersCollection { filter, filter2 }; testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); yield return new object[] { collection, testCase.Object, true, }; // two including filters, match both, should include filter = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: false); filter2 = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: false); collection = new XUnitFiltersCollection { filter, filter2 }; testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); yield return new object[] { collection, testCase.Object, false, }; // one filter that includes, other that excludes, match both, should include filter = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: true); filter2 = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: false); collection = new XUnitFiltersCollection { filter, filter2 }; testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); yield return new object[] { collection, testCase.Object, false, }; // one filter that includes, other that excludes { // match including, should include var excludedTestDisplayName = $"excluded_{testDisplayName}"; filter = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: false); filter2 = XUnitFilter.CreateSingleFilter( singleTestName: excludedTestDisplayName, exclude: true); collection = new XUnitFiltersCollection { filter, filter2 }; // match including, should include testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); yield return new object[] { collection, testCase.Object, false, }; // match excluding, should exclude testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(excludedTestDisplayName); yield return new object[] { collection, testCase.Object, true, }; } // name filter that excludes, trait filter that includes { var traitName = "testTrait"; filter = XUnitFilter.CreateSingleFilter( singleTestName: testDisplayName, exclude: true); filter2 = XUnitFilter.CreateTraitFilter( traitName: traitName, traitValue: null, exclude: false); collection = new XUnitFiltersCollection { filter, filter2 }; var matchingTestTraits = new Dictionary>() { { traitName, new List() } }; var notTestDisplayName = $"not_{testDisplayName}"; // name match, trait match, should exclude testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); testCase.Setup(t => t.Traits).Returns(matchingTestTraits); yield return new object[] { collection, testCase.Object, true, }; // name match, no trait match, should exclude testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(testDisplayName); yield return new object[] { collection, testCase.Object, true, }; // no name match, trait match, should include testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(notTestDisplayName); testCase.Setup(t => t.Traits).Returns(matchingTestTraits); yield return new object[] { collection, testCase.Object, false, }; // no name match, no trait match, should exclude testCase = new Mock(); testCase.Setup(t => t.DisplayName).Returns(notTestDisplayName); yield return new object[] { collection, testCase.Object, true, }; } } } public static IEnumerable AssemblyFilters { get { // single filter, exclude var currentAssembly = Assembly.GetExecutingAssembly(); var assemblyName = $"{currentAssembly.GetName().Name}.dll"; var assemblyPath = currentAssembly.Location; var assemblyInfo = new TestAssemblyInfo(currentAssembly, assemblyPath); var filter = XUnitFilter.CreateAssemblyFilter(assemblyName: assemblyName!, exclude: true); var collection = new XUnitFiltersCollection { filter }; yield return new object[] { collection, assemblyInfo, true, }; // single filter, include filter = XUnitFilter.CreateAssemblyFilter(assemblyName: assemblyName!, exclude: false); collection = new XUnitFiltersCollection { filter }; yield return new object[] { collection, assemblyInfo, false, }; // two excluding filters filter = XUnitFilter.CreateAssemblyFilter(assemblyName: assemblyName!, exclude: true); var filter2 = XUnitFilter.CreateAssemblyFilter(assemblyName: assemblyName!, exclude: true); collection = new XUnitFiltersCollection { filter, filter2 }; yield return new object[] { collection, assemblyInfo, true, }; // two including filters filter = XUnitFilter.CreateAssemblyFilter(assemblyName: assemblyName!, exclude: false); filter2 = XUnitFilter.CreateAssemblyFilter(assemblyName: assemblyName!, exclude: false); collection = new XUnitFiltersCollection { filter, filter2 }; yield return new object[] { collection, assemblyInfo, false, }; // one filter includes, other excludes filter = XUnitFilter.CreateAssemblyFilter(assemblyName: assemblyName!, exclude: true); filter2 = XUnitFilter.CreateAssemblyFilter(assemblyName: assemblyName!, exclude: false); collection = new XUnitFiltersCollection { filter, filter2 }; yield return new object[] { collection, assemblyInfo, false, }; } } [Theory] [MemberData(nameof(TestCaseFilters), MemberType = typeof(FiltersTestData))] internal void IsExcludedTestCase(XUnitFiltersCollection collection, ITestCase testCase, bool excluded) { var wasExcluded = collection.IsExcluded(testCase); Assert.Equal(excluded, wasExcluded); } [Theory] [MemberData(nameof(AssemblyFilters), MemberType = typeof(FiltersTestData))] internal void IsExcludedAsAssembly(XUnitFiltersCollection collection, TestAssemblyInfo assemblyInfo, bool excluded) { var wasExcluded = collection.IsExcluded(assemblyInfo); Assert.Equal(excluded, wasExcluded); } } [Fact] public void AssemblyFilters() { var collection = new XUnitFiltersCollection(); var assemblies = new[] { "MyFirstAssembly.dll", "SecondAssembly.dll", "ThirdAssembly.exe", }; collection.AddRange(assemblies.Select(a => XUnitFilter.CreateAssemblyFilter(a, true))); var classes = new[] { "FirstClass", "SecondClass", "ThirdClass" }; collection.AddRange(classes.Select(c => XUnitFilter.CreateClassFilter(c, true))); var methods = new[] { "FirstMethod", "SecondMethod" }; collection.AddRange(methods.Select(m => XUnitFilter.CreateSingleFilter(m, true))); var namespaces = new[] { "Namespace" }; collection.AddRange(namespaces.Select(n => XUnitFilter.CreateNamespaceFilter(n, true))); Assert.Equal(assemblies.Length, collection.AssemblyFilters.Count()); } [Fact] public void TestCaseFilters() { var collection = new XUnitFiltersCollection(); var assemblies = new[] { "MyFirstAssembly.dll", "SecondAssembly.dll", "ThirdAssembly.exe", }; collection.AddRange(assemblies.Select(a => XUnitFilter.CreateAssemblyFilter(a, true))); var classes = new[] { "FirstClass", "SecondClass", "ThirdClass" }; collection.AddRange(classes.Select(c => XUnitFilter.CreateClassFilter(c, true))); var methods = new[] { "FirstMethod", "SecondMethod" }; collection.AddRange(methods.Select(m => XUnitFilter.CreateSingleFilter(m, true))); var namespaces = new[] { "Namespace" }; collection.AddRange(namespaces.Select(n => XUnitFilter.CreateNamespaceFilter(n, true))); Assert.Equal(collection.Count - assemblies.Length, collection.TestCaseFilters.Count()); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/AppBundleInformationParserTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Reflection; using System.Threading.Tasks; using System.Xml; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests; public class AppBundleInformationParserTests : IDisposable { private const string AppName = "com.xamarin.bcltests.SystemXunit"; private const string Executable = "SystemXunit.bcltests.xamarin.com"; private static readonly string s_outputPath = Path.GetDirectoryName(Assembly.GetAssembly(typeof(AppBundleInformationParser)).Location); private static readonly string s_sampleProjectPath = Path.Combine(s_outputPath, "Samples", "TestProject"); private static readonly string s_appPath = Path.Combine(s_sampleProjectPath, "bin", AppName + ".app"); private static readonly string s_appPath2 = Path.Combine(s_sampleProjectPath, "bin2", AppName + ".app"); private static readonly string s_projectFilePath = Path.Combine(s_sampleProjectPath, "SystemXunit.csproj"); public AppBundleInformationParserTests() { Directory.CreateDirectory(s_appPath); Directory.CreateDirectory(s_appPath2); } public void Dispose() { Directory.Delete(s_appPath, true); Directory.Delete(s_appPath2, true); GC.SuppressFinalize(this); } [Fact] public async Task ParseFromProjectTest() { var parser = new AppBundleInformationParser(Mock.Of()); var info = await parser.ParseFromProject(s_projectFilePath, TestTarget.Simulator_iOS64, "Debug"); Assert.Equal(AppName, info.AppName); Assert.Equal(s_appPath, info.AppPath); Assert.Equal(s_appPath, info.LaunchAppPath); Assert.Equal(AppName, info.BundleIdentifier); } [Fact] public async Task ParseFromMacCatalystProjectTest() { var parser = new AppBundleInformationParser(Mock.Of()); var info = await parser.ParseFromProject(s_projectFilePath, TestTarget.MacCatalyst, "Debug"); Assert.Equal(AppName, info.AppName); Assert.Equal(s_appPath, info.AppPath); Assert.Equal(s_appPath, info.LaunchAppPath); Assert.Equal(AppName, info.BundleIdentifier); Assert.Equal(Executable, info.BundleExecutable); } [Fact] public async Task ParseFromProjectWithLocatorTest() { var locator = new Mock(); locator .Setup(x => x.LocateAppBundle(It.IsAny(), s_projectFilePath, TestTarget.Simulator_iOS64, "Debug")) .ReturnsAsync("bin2") .Verifiable(); var parser = new AppBundleInformationParser(Mock.Of(), locator.Object); var info = await parser.ParseFromProject(s_projectFilePath, TestTarget.Simulator_iOS64, "Debug"); Assert.Equal(AppName, info.AppName); Assert.Equal(s_appPath2, info.AppPath); Assert.Equal(s_appPath2, info.LaunchAppPath); Assert.Equal(AppName, info.BundleIdentifier); locator.VerifyAll(); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/CrashSnapshotReporterTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.Common.Utilities; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests; public class CrashReportSnapshotTests : IDisposable { private readonly string _tempXcodeRoot; private readonly string _symbolicatePath; private readonly Mock _processManager; private readonly Mock _log; private readonly Mock _logs; public CrashReportSnapshotTests() { _processManager = new Mock(); _log = new Mock(); _logs = new Mock(); _tempXcodeRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); _symbolicatePath = Path.Combine(_tempXcodeRoot, "Contents", "SharedFrameworks", "DTDeviceKitBase.framework", "Versions", "A", "Resources"); _processManager.SetupGet(x => x.XcodeRoot).Returns(_tempXcodeRoot); _processManager.SetupGet(x => x.MlaunchPath).Returns("/var/bin/mlaunch"); // Create fake place for device logs Directory.CreateDirectory(_tempXcodeRoot); // Create fake symbolicate binary Directory.CreateDirectory(_symbolicatePath); File.WriteAllText(Path.Combine(_symbolicatePath, "symbolicatecrash"), ""); } public void Dispose() { Directory.Delete(_tempXcodeRoot, true); GC.SuppressFinalize(this); } [Fact] public async Task DeviceCaptureTest() { var tempFilePath = Path.GetTempFileName(); const string deviceName = "Sample-iPhone"; const string crashLogPath = "/path/to/crash.log"; const string symbolicateLogPath = "/path/to/" + deviceName + ".symbolicated.log"; var crashReport = Mock.Of(x => x.FullPath == crashLogPath); var symbolicateReport = Mock.Of(x => x.FullPath == symbolicateLogPath); // Crash report is added _logs.Setup(x => x.Create(deviceName, "Crash report: " + deviceName, It.IsAny())) .Returns(crashReport); // Symbolicate report is added _logs.Setup(x => x.Create("crash.symbolicated.log", "Symbolicated crash report: crash.log", It.IsAny())) .Returns(symbolicateReport); _processManager.SetReturnsDefault(Task.FromResult(new ProcessExecutionResult() { ExitCode = 0 })); // Act var snapshotReport = new CrashSnapshotReporter(_processManager.Object, _log.Object, _logs.Object, true, deviceName, () => tempFilePath); File.WriteAllLines(tempFilePath, new[] { "crash 1", "crash 2" }); await snapshotReport.StartCaptureAsync(); File.WriteAllLines(tempFilePath, new[] { "Sample-iPhone" }); await snapshotReport.EndCaptureAsync(TimeSpan.FromSeconds(10)); // Verify _logs.VerifyAll(); // List of crash reports is retrieved _processManager.Verify( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == StringUtils.FormatArguments( $"--list-crash-reports={tempFilePath}") + " " + $"--devname {StringUtils.FormatArguments(deviceName)}"), _log.Object, TimeSpan.FromMinutes(1), null, It.IsAny(), null), Times.Exactly(2)); // Device crash log is downloaded _processManager.Verify( x => x.ExecuteCommandAsync( It.Is(args => args.AsCommandLine() == StringUtils.FormatArguments( $"--download-crash-report={deviceName}") + " " + StringUtils.FormatArguments($"--download-crash-report-to={crashLogPath}") + " " + $"--devname {StringUtils.FormatArguments(deviceName)}"), _log.Object, TimeSpan.FromMinutes(1), null, It.IsAny(), null), Times.Once); // Symbolicate is ran _processManager.Verify( x => x.ExecuteCommandAsync( Path.Combine(_symbolicatePath, "symbolicatecrash"), It.Is>(args => args.First() == crashLogPath), symbolicateReport, TimeSpan.FromMinutes(1), It.IsAny>(), null), Times.Once); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Execution/MlaunchArgumentsTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using Microsoft.DotNet.XHarness.Common.Utilities; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Execution; public class MlaunchArgumentsTests { public class CommandLineDataTestSource { private static readonly string s_listDevFile = "/my/listdev.txt"; private static readonly string s_listSimFile = "/my/listsim.txt"; private static readonly string s_xmlOutputType = "XML"; public static IEnumerable CommandLineArgs => new[] { new object[] { new MlaunchArgument[] { new ListDevicesArgument (s_listDevFile) }, $"--listdev={s_listDevFile}" }, new object[] { new MlaunchArgument[] { new ListSimulatorsArgument (s_listSimFile) }, $"--listsim={s_listSimFile}" }, new object[] { new MlaunchArgument[] { new XmlOutputFormatArgument () }, $"--output-format={s_xmlOutputType}" }, new object[] { new MlaunchArgument[] { new ListExtraDataArgument () }, "--list-extra-data" }, new object[] { new MlaunchArgument[] { new DownloadCrashReportToArgument ("/path/with spaces.txt"), new DeviceNameArgument ("Test iPad") }, $"\"--download-crash-report-to=/path/with spaces.txt\" --devname \"Test iPad\"" }, new object[] { new MlaunchArgument[] { new SetEnvVariableArgument ("SOME_PARAM", "true"), new SetEnvVariableArgument ("NUNIT_LOG_FILE", "/another space/path.txt") }, $"-setenv=SOME_PARAM=true \"-setenv=NUNIT_LOG_FILE=/another space/path.txt\"" }, new object[] { new MlaunchArgument[] { new ListDevicesArgument (s_listDevFile), new XmlOutputFormatArgument (), new ListExtraDataArgument () }, $"--listdev={s_listDevFile} --output-format={s_xmlOutputType} --list-extra-data" }, }; }; [Theory] [MemberData(nameof(CommandLineDataTestSource.CommandLineArgs), MemberType = typeof(CommandLineDataTestSource))] public void AsCommandLineTest(MlaunchArgument[] args, string expected) => Assert.Equal(expected, new MlaunchArguments(args).AsCommandLine()); [Fact] public void MlaunchArgumentAndProcessManagerTest() { var oldArgs = new List() { "--download-crash-report-to=/path/with spaces.txt", "--sdkroot", "/path to xcode/spaces", "--devname", "Premek's iPhone", }; var newArgs = new MlaunchArguments() { new DownloadCrashReportToArgument ("/path/with spaces.txt"), new SdkRootArgument ("/path to xcode/spaces"), new DeviceNameArgument ("Premek's iPhone"), }; var oldWayOfPassingArgs = StringUtils.FormatArguments(oldArgs); var newWayOfPassingArgs = newArgs.AsCommandLine(); Assert.Equal(oldWayOfPassingArgs, newWayOfPassingArgs); } [Fact] public void MlaunchArgumentEqualityTest() { var arg1 = new DownloadCrashReportToArgument("/path/with spaces.txt"); var arg2 = new DownloadCrashReportToArgument("/path/with spaces.txt"); var arg3 = new DownloadCrashReportToArgument("/path/with.txt"); Assert.Equal(arg1, arg2); Assert.NotEqual(arg1, arg3); } [Fact] public void MlaunchArgumentsEqualityTest() { var args1 = new MlaunchArgument[] { new ListDevicesArgument ("foo"), new ListSimulatorsArgument ("bar") }; var args2 = new MlaunchArgument[] { new ListDevicesArgument ("foo"), new ListSimulatorsArgument ("bar") }; var args3 = new MlaunchArgument[] { new ListDevicesArgument ("foo"), new ListSimulatorsArgument ("xyz") }; Assert.Equal(args1, args2); Assert.NotEqual(args1, args3); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Hardware/DefaultSimulatorSelectorTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Hardware; public class DefaultSimulatorSelectorTests { private readonly Mock _processManager; private readonly Mock _tccDatabase; private readonly DefaultSimulatorSelector _simulatorSelector; public DefaultSimulatorSelectorTests() { _processManager = new Mock(); _tccDatabase = new Mock(); _simulatorSelector = new DefaultSimulatorSelector(); } [Fact] public void SelectSimulatorTest() { var simulator1 = new SimulatorDevice(_processManager.Object, _tccDatabase.Object) { Name = "Simulator 1", UDID = "udid1", State = DeviceState.Shutdown, }; var simulator2 = new SimulatorDevice(_processManager.Object, _tccDatabase.Object) { Name = "Simulator 2", UDID = "udid2", State = DeviceState.Booted, }; var simulator3 = new SimulatorDevice(_processManager.Object, _tccDatabase.Object) { Name = "Simulator 3", UDID = "udid3", State = DeviceState.Booting, }; var simulator = _simulatorSelector.SelectSimulator(new[] { simulator1, simulator2, simulator3 }); // The Booted one Assert.Equal(simulator2, simulator); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Hardware/DeviceTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Hardware; public class DeviceTest { public class DevicesDataTestSource { public static IEnumerable DebugSpeedDevices { get { var data = new[] { (interfaceType: "usb", result: 0), (interfaceType: "USB", result: 0), (interfaceType: "wifi", result: 2), (interfaceType: "WIFI", result: 2), (interfaceType: (string) null, result: 1), (interfaceType: "HOLA", result: 3), }; foreach (var (interfaceType, result) in data) { yield return new object[] { new Device( buildVersion: "17A577", deviceClass: DeviceClass.iPhone, deviceIdentifier: "8A450AA31EA94191AD6B02455F377CC1", interfaceType: interfaceType, isUsableForDebugging: true, name: "Test iPhone", productType: "iPhone12,1", productVersion: "13.0"), result }; } } } public static IEnumerable DevicePlatformDevices { get { var data = new[] { (deviceClass: DeviceClass.iPhone, result: DevicePlatform.iOS), (deviceClass: DeviceClass.iPod, result: DevicePlatform.iOS), (deviceClass: DeviceClass.iPad, result: DevicePlatform.iOS), (deviceClass: DeviceClass.AppleTV, result: DevicePlatform.tvOS), (deviceClass: DeviceClass.Watch, result: DevicePlatform.watchOS), (deviceClass: DeviceClass.xrOS, result: DevicePlatform.xrOS), (deviceClass: DeviceClass.Unknown, result: DevicePlatform.Unknown), }; foreach (var (deviceClass, result) in data) { yield return new object[] { new Device( buildVersion: "17A577", deviceClass: deviceClass, deviceIdentifier: "8A450AA31EA94191AD6B02455F377CC1", interfaceType: "USB", isUsableForDebugging: true, name: "Test iPhone", productType: "iPhone12,1", productVersion: "13.0"), result }; } } } public static IEnumerable Supports64bDevices { get { var data = new Dictionary() { ["iPhone"] = new[] { (version: "1,1", result: false), (version: "1,2", result: false), (version: "2,1", result: false), (version: "3,1", result: false), (version: "3,2", result: false), (version: "3,3", result: false), (version: "4,1", result: false), (version: "5,1", result: false), (version: "5,2", result: false), (version: "5,3", result: false), (version: "6,1", result: true), (version: "6,2", result: true), (version: "7,1", result: true), (version: "7,2", result: true), (version: "8,4", result: true), (version: "9,1", result: true), (version: "9,2", result: true), (version: "10,1", result: true), (version: "11,1", result: true), (version: "12,1", result: true), }, ["iPad"] = new[] { (version: "1,1", result: false), (version: "1,2", result: false), (version: "2,1", result: false), (version: "3,1", result: false), (version: "3,2", result: false), (version: "3,3", result: false), (version: "4,1", result: true), (version: "4,2", result: true), (version: "5,1", result: true), (version: "6,1", result: true), (version: "6,3", result: true), (version: "7,1", result: true), }, ["iPod"] = new[] { (version: "1,1", result: false), (version: "1,2", result: false), (version: "2,1", result: false), (version: "3,3", result: false), (version: "4,1", result: false), (version: "5,1", result: false), (version: "5,2", result: false), (version: "7,1", result: true), (version: "7,2", result: true), }, ["AppleTV"] = new[] { (version: "1,1", result: true), (version: "2,1", result: true), (version: "3,1", result: true), }, ["Watch"] = new[] { (version: "1,1", result: false), (version: "1,2", result: false), (version: "2,1", result: false), (version: "3,1", result: false), (version: "3,2", result: false), (version: "3,3", result: false), (version: "4,1", result: false), (version: "4,2", result: false), } }; foreach (var product in data.Keys) { foreach (var (version, result) in data[product]) { yield return new object[] { new Device( buildVersion: "17A577", deviceClass: DeviceClass.iPhone, deviceIdentifier: "8A450AA31EA94191AD6B02455F377CC1", interfaceType: "USB", isUsableForDebugging: true, name: "Test iPhone", productType: $"{product}{version}", productVersion: "13.0"), result }; } } } } public static IEnumerable Supports32bDevices { get { var iOSCommon = new[] { (version: new Version (1,1), result: true), (version: new Version (2,1), result: true), (version: new Version (3,1), result: true), (version: new Version (4,1), result: true), (version: new Version (5,1), result: true), (version: new Version (6,1), result: true), (version: new Version (7,1), result: true), (version: new Version (8,1), result: true), (version: new Version (11,1), result: false), (version: new Version (11,2), result: false), (version: new Version (12,1), result: false), }; var data = new Dictionary { [DeviceClass.iPhone] = iOSCommon, [DeviceClass.iPad] = iOSCommon, [DeviceClass.iPod] = iOSCommon, [DeviceClass.AppleTV] = new[] { (version: new Version (1,1), result: false), (version: new Version (2,1), result: false), (version: new Version (3,1), result: false), (version: new Version (4,1), result: false), }, [DeviceClass.Watch] = new[] { (version: new Version (1,1), result: true), (version: new Version (2,1), result: true), (version: new Version (3,1), result: true), (version: new Version (4,1), result: true), } }; foreach (var deviceClass in data.Keys) { foreach (var (version, result) in data[deviceClass]) { yield return new object[] { new Device( buildVersion: "17A577", deviceClass: deviceClass, deviceIdentifier: "8A450AA31EA94191AD6B02455F377CC1", interfaceType: "USB", isUsableForDebugging: true, name: "Test iPhone", productType: "iPhone12,1", productVersion: version.ToString()), result }; } } } } [Theory] [MemberData(nameof(DebugSpeedDevices), MemberType = typeof(DevicesDataTestSource))] public void DebugSpeedTest(IHardwareDevice device, int expected) => Assert.Equal(expected, device.DebugSpeed); [Theory] [MemberData(nameof(DevicePlatformDevices), MemberType = typeof(DevicesDataTestSource))] public void DevicePlatformTest(IHardwareDevice device, DevicePlatform expected) => Assert.Equal(expected, device.DevicePlatform); [Theory] [MemberData(nameof(Supports64bDevices), MemberType = typeof(DevicesDataTestSource))] public void Supports64bTest(IHardwareDevice device, bool expected) => Assert.Equal(expected, device.Supports64Bit); [Theory] [MemberData(nameof(Supports32bDevices), MemberType = typeof(DevicesDataTestSource))] public void Supports32BTest(IHardwareDevice device, bool expected) => Assert.Equal(expected, device.Supports32Bit); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Hardware/HardwareDeviceLoaderTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Hardware; public class HardwareDeviceLoaderTests { private readonly HardwareDeviceLoader _devices; private readonly Mock _processManager; private readonly Mock _executionLog; public HardwareDeviceLoaderTests() { _processManager = new Mock(); _devices = new HardwareDeviceLoader(_processManager.Object); _executionLog = new Mock(); } [Theory] [InlineData(false)] // no timeout [InlineData(true)] // timeoout public async Task LoadAsyncProcessErrorTest(bool timeout) { string processPath = null; MlaunchArguments passedArguments = null; // moq It.Is is not working as nicelly as we would like it, we capture data and use asserts _processManager.Setup(p => p.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?, bool?>((p, args, log, t, env, verbosity, token, d) => { // we are going set the used args to validate them later, will always return an error from this method processPath = p.StartInfo.FileName; passedArguments = args; if (!timeout) { return Task.FromResult(new ProcessExecutionResult { ExitCode = 1, TimedOut = false }); } else { return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = true }); } }); await Assert.ThrowsAsync(async () => { await _devices.LoadDevices(_executionLog.Object); }); MlaunchArgument listDevArg = passedArguments.Where(a => a is ListDevicesArgument).FirstOrDefault(); Assert.NotNull(listDevArg); MlaunchArgument outputFormatArg = passedArguments.Where(a => a is XmlOutputFormatArgument).FirstOrDefault(); Assert.NotNull(outputFormatArg); } [Theory] [InlineData(true)] [InlineData(false)] public async Task LoadAsyncProcessSuccess(bool extraData) { string processPath = null; MlaunchArguments passedArguments = null; // moq It.Is is not working as nicelly as we would like it, we capture data and use asserts _processManager.Setup(p => p.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?, bool?>((p, args, log, t, env, verbosity, token, d) => { processPath = p.StartInfo.FileName; passedArguments = args; // we get the temp file that was passed as the args, and write our sample xml, which will be parsed to get the devices :) var tempPath = args.Where(a => a is ListDevicesArgument).First().AsCommandLineArgument(); tempPath = tempPath.Substring(tempPath.IndexOf('=') + 1).Replace("\"", string.Empty); var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith("devices.xml", StringComparison.Ordinal)).FirstOrDefault(); using (var outputStream = new StreamWriter(tempPath)) using (var sampleStream = new StreamReader(GetType().Assembly.GetManifestResourceStream(name))) { string line; while ((line = sampleStream.ReadLine()) != null) { outputStream.WriteLine(line); } } return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false }); }); await _devices.LoadDevices(_executionLog.Object, listExtraData: extraData); // assert the devices that are expected from the sample xml MlaunchArgument listDevArg = passedArguments.Where(a => a is ListDevicesArgument).FirstOrDefault(); Assert.NotNull(listDevArg); MlaunchArgument outputFormatArg = passedArguments.Where(a => a is XmlOutputFormatArgument).FirstOrDefault(); Assert.NotNull(outputFormatArg); if (extraData) { MlaunchArgument listExtraDataArg = passedArguments.Where(a => a is ListExtraDataArgument).FirstOrDefault(); Assert.NotNull(listExtraDataArg); } Assert.Equal(2, _devices.Connected64BitIOS.Count()); Assert.Single(_devices.Connected32BitIOS); Assert.Empty(_devices.ConnectedTV); } [Fact] public async Task FindAndCacheDevicesWithFailingMlaunchTest() { string processPath = null; MlaunchArguments passedArguments = null; // Moq.SetupSequence doesn't allow custom callbacks so we need to count ourselves var calls = 0; // moq It.Is is not working as nicelly as we would like it, we capture data and use asserts _processManager.Setup(p => p.RunAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?, bool?>((p, args, log, t, env, verbosity, token, d) => { calls++; if (calls == 1) { // Mlaunch can sometimes time out and we are testing that a subsequent Load will trigger it again return Task.FromResult(new ProcessExecutionResult { ExitCode = 137, TimedOut = true }); } processPath = p.StartInfo.FileName; passedArguments = args; // we get the temp file that was passed as the args, and write our sample xml, which will be parsed to get the devices :) var tempPath = args.Where(a => a is ListDevicesArgument).First().AsCommandLineArgument(); tempPath = tempPath.Substring(tempPath.IndexOf('=') + 1).Replace("\"", string.Empty); var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith("devices.xml", StringComparison.Ordinal)).FirstOrDefault(); using (var outputStream = new StreamWriter(tempPath)) using (var sampleStream = new StreamReader(GetType().Assembly.GetManifestResourceStream(name))) { string line; while ((line = sampleStream.ReadLine()) != null) outputStream.WriteLine(line); } return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false }); }); await Assert.ThrowsAsync(async () => await _devices.LoadDevices(_executionLog.Object)); Assert.Empty(_devices.ConnectedDevices); Assert.Equal(1, calls); await _devices.LoadDevices(_executionLog.Object); Assert.Equal(2, calls); Assert.NotEmpty(_devices.ConnectedDevices); await _devices.LoadDevices(_executionLog.Object); Assert.Equal(2, calls); await _devices.LoadDevices(_executionLog.Object); Assert.Equal(2, calls); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Hardware/SimulatorDeviceTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Hardware; public class SimulatorDeviceTest { private readonly Mock _executionLog; private readonly Mock _processManager; private readonly SimulatorDevice _simulator; public SimulatorDeviceTest() { _executionLog = new Mock(); _processManager = new Mock(); _simulator = new SimulatorDevice(_processManager.Object, new TCCDatabase(_processManager.Object)) { UDID = Guid.NewGuid().ToString() }; } [Theory] [InlineData("com.apple.CoreSimulator.SimRuntime.watchOS-5-1", true)] [InlineData("com.apple.CoreSimulator.SimRuntime.iOS-7-1", false)] public void IsWatchSimulatorTest(string runtime, bool expectation) { _simulator.SimRuntime = runtime; Assert.Equal(expectation, _simulator.IsWatchSimulator); } [Theory] [InlineData("com.apple.CoreSimulator.SimRuntime.iOS-12-1", "iOS 12.1")] [InlineData("com.apple.CoreSimulator.SimRuntime.iOS-10-1", "iOS 10.1")] public void OSVersionTest(string runtime, string expected) { _simulator.SimRuntime = runtime; Assert.Equal(expected, _simulator.OSVersion); } [Fact] public async Task EraseAsyncTest() { // just call and verify the correct args are pass await _simulator.Erase(_executionLog.Object); _processManager.Verify(h => h.ExecuteXcodeCommandAsync(It.Is(s => s == "simctl"), It.Is(args => args.Where(a => a == _simulator.UDID || a == "shutdown").Count() == 2), It.IsAny(), It.IsAny(), It.IsAny())); _processManager.Verify(h => h.ExecuteXcodeCommandAsync(It.Is(s => s == "simctl"), It.Is(args => args.Where(a => a == _simulator.UDID || a == "erase").Count() == 2), It.IsAny(), It.IsAny(), It.IsAny())); _processManager.Verify(h => h.ExecuteXcodeCommandAsync(It.Is(s => s == "simctl"), It.Is(args => args.Where(a => a == _simulator.UDID || a == "boot").Count() == 2), It.IsAny(), It.IsAny(), It.IsAny())); _processManager.Verify(h => h.ExecuteXcodeCommandAsync(It.Is(s => s == "simctl"), It.Is(args => args.Where(a => a == _simulator.UDID || a == "shutdown").Count() == 2), It.IsAny(), It.IsAny(), It.IsAny())); } [Fact] public async Task ShutdownAsyncTest() { await _simulator.Shutdown(_executionLog.Object); // just call and verify the correct args are pass _processManager.Verify(h => h.ExecuteXcodeCommandAsync(It.Is(s => s == "simctl"), It.Is(args => args.Where(a => a == _simulator.UDID || a == "shutdown").Count() == 2), It.IsAny(), It.IsAny(), It.IsAny())); } [Fact(Skip = "Running this test will actually kill simulators on the machine")] public async Task KillEverythingAsyncTest() { Func, bool> verifyKillAll = (args) => { var toKill = new List { "-9", "iPhone Simulator", "iOS Simulator", "Simulator", "Simulator (Watch)", "com.apple.CoreSimulator.CoreSimulatorService", "ibtoold" }; return args.Where(a => toKill.Contains(a)).Count() == toKill.Count; }; var simulator = new SimulatorDevice(_processManager.Object, new TCCDatabase(_processManager.Object)); await simulator.KillEverything(_executionLog.Object); // verify that all the diff process have been killed making sure args are correct _processManager.Verify(p => p.ExecuteCommandAsync(It.Is(s => s == "launchctl"), It.Is(args => args.Where(a => a == "remove" || a == "com.apple.CoreSimulator.CoreSimulatorService").Count() == 2), It.IsAny(), It.IsAny(), null, null)); _processManager.Verify(p => p.ExecuteCommandAsync(It.Is(s => s == "killall"), It.Is>(a => verifyKillAll(a)), It.IsAny(), It.IsAny(), null, null)); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Hardware/SimulatorLoaderTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Hardware; public class SimulatorLoaderTests { private readonly Mock _executionLog; private readonly Mock _processManager; private readonly SimulatorLoader _simulatorLoader; public SimulatorLoaderTests() { _executionLog = new Mock(); _processManager = new Mock(); _simulatorLoader = new SimulatorLoader(_processManager.Object); } [Fact] public async Task LoadAsyncProcessErrorTest() { MlaunchArguments passedArguments = null; // moq It.Is is not working as nicelly as we would like it, we capture data and use asserts _processManager .Setup(p => p.ExecuteCommandAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?>((args, log, t, env, verbosity, token) => { // we are going set the used args to validate them later, will always return an error from this method passedArguments = args; return Task.FromResult(new ProcessExecutionResult { ExitCode = 1, TimedOut = false }); }); await Assert.ThrowsAsync(async () => { await _simulatorLoader.LoadDevices(_executionLog.Object); }); // validate the execution of mlaunch MlaunchArgument listSimArg = passedArguments.Where(a => a is ListSimulatorsArgument).FirstOrDefault(); Assert.NotNull(listSimArg); MlaunchArgument outputFormatArg = passedArguments.Where(a => a is XmlOutputFormatArgument).FirstOrDefault(); Assert.NotNull(outputFormatArg); } private void CopySampleData(string tempPath) { var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith("simulators.xml", StringComparison.Ordinal)).FirstOrDefault(); using (var outputStream = new StreamWriter(tempPath)) using (var sampleStream = new StreamReader(GetType().Assembly.GetManifestResourceStream(name))) { string line; while ((line = sampleStream.ReadLine()) != null) { line = line.Replace("{{MAX-IOS.VERSION}}", SdkVersions.MaxiOSDeploymentTarget); line = line.Replace("{{MAX-IOS-VERSION}}", SdkVersions.MaxiOSDeploymentTarget.Replace(".", "-")); line = line.Replace("{{MAX-WATCH.VERSION}}", SdkVersions.MaxWatchDeploymentTarget); line = line.Replace("{{MAX-WATCH-VERSION}}", SdkVersions.MaxWatchDeploymentTarget.Replace(".", "-")); line = line.Replace("{{MAX-TVOS.VERSION}}", SdkVersions.MaxTVOSDeploymentTarget); line = line.Replace("{{MAX-TVOS-VERSION}}", SdkVersions.MaxTVOSDeploymentTarget.Replace(".", "-")); outputStream.WriteLine(line); } } } [Fact] public async Task LoadAsyncProcessSuccess() { MlaunchArguments passedArguments = null; // moq It.Is is not working as nicelly as we would like it, we capture data and use asserts _processManager.Setup(p => p.ExecuteCommandAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?>((args, log, t, env, verbosity, token) => { passedArguments = args; // we get the temp file that was passed as the args, and write our sample xml, which will be parsed to get the devices :) var tempPath = args.Where(a => a is ListSimulatorsArgument).First().AsCommandLineArgument(); tempPath = tempPath.Substring(tempPath.IndexOf('=') + 1).Replace("\"", string.Empty); CopySampleData(tempPath); return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false }); }); await _simulatorLoader.LoadDevices(_executionLog.Object); MlaunchArgument listSimArg = passedArguments.Where(a => a is ListSimulatorsArgument).FirstOrDefault(); Assert.NotNull(listSimArg); MlaunchArgument outputFormatArg = passedArguments.Where(a => a is XmlOutputFormatArgument).FirstOrDefault(); Assert.NotNull(outputFormatArg); Assert.Equal(76, _simulatorLoader.AvailableDevices.Count()); } [Theory] [InlineData(TestTarget.Simulator_iOS64, false)] [InlineData(TestTarget.Simulator_tvOS, false)] [InlineData(TestTarget.Simulator_watchOS, true)] public async Task FindAsyncDoNotCreateTest(TestTarget target, bool shouldFindCompanion) { MlaunchArguments passedArguments = null; _processManager .Setup(h => h.ExecuteXcodeCommandAsync("simctl", It.Is(args => args[0] == "create"), _executionLog.Object, TimeSpan.FromMinutes(1), It.IsAny())) .ReturnsAsync(new ProcessExecutionResult() { ExitCode = 0 }); // moq It.Is is not working as nicelly as we would like it, we capture data and use asserts _processManager .Setup(p => p.ExecuteCommandAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?>((args, log, t, env, verbosity, token) => { passedArguments = args; // we get the temp file that was passed as the args, and write our sample xml, which will be parsed to get the devices :) var tempPath = args.Where(a => a is ListSimulatorsArgument).First().AsCommandLineArgument(); tempPath = tempPath.Substring(tempPath.IndexOf('=') + 1).Replace("\"", string.Empty); CopySampleData(tempPath); return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false }); }); await _simulatorLoader.LoadDevices(_executionLog.Object); var (simulator, companion) = await _simulatorLoader.FindSimulators(target, _executionLog.Object, false, false); Assert.NotNull(simulator); if (shouldFindCompanion) { Assert.NotNull(companion); } else { Assert.Null(companion); } } [Fact] public async Task FindAsyncExactVersionNotFound() { MlaunchArguments passedArguments = null; _processManager .Setup(h => h.ExecuteXcodeCommandAsync("simctl", It.Is(args => args[0] == "create"), _executionLog.Object, TimeSpan.FromMinutes(1), It.IsAny())) .ReturnsAsync(new ProcessExecutionResult() { ExitCode = 0 }); // moq It.Is is not working as nicelly as we would like it, we capture data and use asserts _processManager .Setup(p => p.ExecuteCommandAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?>((args, log, t, env, verbosity, token) => { passedArguments = args; // we get the temp file that was passed as the args, and write our sample xml, which will be parsed to get the devices :) var tempPath = args.Where(a => a is ListSimulatorsArgument).First().AsCommandLineArgument(); tempPath = tempPath.Substring(tempPath.IndexOf('=') + 1).Replace("\"", string.Empty); CopySampleData(tempPath); return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false }); }); await _simulatorLoader.LoadDevices(_executionLog.Object); await Assert.ThrowsAsync(async () => await _simulatorLoader.FindSimulators(new TestTargetOs(TestTarget.Simulator_iOS64, "12.8"), _executionLog.Object, false, false)); } [Fact] public async Task FindAsyncExactVersionFound() { MlaunchArguments passedArguments = null; _processManager .Setup(h => h.ExecuteXcodeCommandAsync("simctl", It.Is(args => args[0] == "create"), _executionLog.Object, TimeSpan.FromMinutes(1), It.IsAny())) .ReturnsAsync(new ProcessExecutionResult() { ExitCode = 0 }); // moq It.Is is not working as nicelly as we would like it, we capture data and use asserts _processManager .Setup(p => p.ExecuteCommandAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?>((args, log, t, env, verbosity, token) => { passedArguments = args; // we get the temp file that was passed as the args, and write our sample xml, which will be parsed to get the devices :) var tempPath = args.Where(a => a is ListSimulatorsArgument).First().AsCommandLineArgument(); tempPath = tempPath.Substring(tempPath.IndexOf('=') + 1).Replace("\"", string.Empty); CopySampleData(tempPath); return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false }); }); await _simulatorLoader.LoadDevices(_executionLog.Object); var (simulator, _) = await _simulatorLoader.FindSimulators(new TestTargetOs(TestTarget.Simulator_iOS64, SdkVersions.MaxiOSSimulator), _executionLog.Object, false, false); Assert.NotNull(simulator); } // This tests the SimulatorEnumerable [Theory] [InlineData(TestTarget.Simulator_iOS64)] [InlineData(TestTarget.Simulator_tvOS)] public void SelectDevicesDeviceOnlyTest(TestTarget testTarget) { // moq It.Is is not working as nicelly as we would like it, we capture data and use asserts _processManager .Setup(p => p.ExecuteCommandAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?>((args, log, t, env, verbosity, token) => { // We get the temp file that was passed as the args, and write our sample xml, which will be parsed to get the devices :) var tempPath = args.Where(a => a is ListSimulatorsArgument).First().AsCommandLineArgument(); tempPath = tempPath.Substring(tempPath.IndexOf('=') + 1).Replace("\"", string.Empty); CopySampleData(tempPath); return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false }); }); var devices = _simulatorLoader.SelectDevices(testTarget, _executionLog.Object, false).ToList(); Assert.Single(devices); } // This tests the SimulatorEnumerable [Fact] public void SelectDevicesDeviceAndCompanionTest() { // moq It.Is is not working as nicelly as we would like it, we capture data and use asserts _processManager .Setup(p => p.ExecuteCommandAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?>((args, log, t, env, verbosity, token) => { // We get the temp file that was passed as the args, and write our sample xml, which will be parsed to get the devices :) var tempPath = args.Where(a => a is ListSimulatorsArgument).First().AsCommandLineArgument(); tempPath = tempPath.Substring(tempPath.IndexOf('=') + 1).Replace("\"", string.Empty); CopySampleData(tempPath); return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false }); }); var devices = _simulatorLoader.SelectDevices(TestTarget.Simulator_watchOS, _executionLog.Object, false).ToList(); Assert.Equal(2, devices.Count); Assert.True(devices.First().IsWatchSimulator); Assert.False(devices.Last().IsWatchSimulator); } // This tests issues with mlaunch https://github.com/dotnet/xharness/issues/196 // Mlaunch sometimes times out/returns non-zero exit code and still outputs correct XML [Theory] [InlineData(0, true)] [InlineData(137, false)] [InlineData(1, true)] public async Task FindSimulatorsWithSucceedingMlaunchTest(int mlaunchExitCode, bool mlaunchTimedout) { _processManager .Setup(h => h.ExecuteXcodeCommandAsync("simctl", It.Is(args => args[0] == "create"), _executionLog.Object, TimeSpan.FromMinutes(1), It.IsAny())) .ReturnsAsync(new ProcessExecutionResult() { ExitCode = 0 }); // moq It.Is is not working as nicelly as we would like it, we capture data and use asserts _processManager .Setup(p => p.ExecuteCommandAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?>((args, log, t, env, verbosity, token) => { // we get the temp file that was passed as the args, and write our sample xml, which will be parsed to get the devices :) var tempPath = args.Where(a => a is ListSimulatorsArgument).First().AsCommandLineArgument(); tempPath = tempPath.Substring(tempPath.IndexOf('=') + 1).Replace("\"", string.Empty); CopySampleData(tempPath); return Task.FromResult(new ProcessExecutionResult { ExitCode = mlaunchExitCode, TimedOut = mlaunchTimedout }); }); await _simulatorLoader.LoadDevices(_executionLog.Object); var (simulator, companion) = await _simulatorLoader.FindSimulators(TestTarget.Simulator_iOS64, _executionLog.Object, false, false); Assert.NotNull(simulator); } // This tests issues with mlaunch https://github.com/dotnet/xharness/issues/196 and https://github.com/dotnet/xharness/issues/283 // Mlaunch sometimes times out/returns non-zero exit code and doesn't output simulator list XML (#196), // retry then should succeed (#283, #288). [Fact] public async Task FindSimulatorsWithFailingMlaunchTest() { // Moq.SetupSequence doesn't allow custom callbacks so we need to count ourselves var calls = 0; _processManager .Setup(p => p.ExecuteCommandAsync(It.Is(args => args.Any(a => a is ListSimulatorsArgument)), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns, int, CancellationToken?>((args, log, t, env, verbosity, token) => { calls++; if (calls == 1) { return Task.FromResult(new ProcessExecutionResult { ExitCode = 137, TimedOut = true }); } // we get the temp file that was passed as the args, and write our sample xml, which will be parsed to get the devices :) var tempPath = args.Where(a => a is ListSimulatorsArgument).First().AsCommandLineArgument(); tempPath = tempPath.Substring(tempPath.IndexOf('=') + 1).Replace("\"", string.Empty); CopySampleData(tempPath); return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false }); }); await Assert.ThrowsAsync(async () => await _simulatorLoader.LoadDevices(_executionLog.Object)); Assert.Empty(_simulatorLoader.AvailableDevices); Assert.Equal(1, calls); await _simulatorLoader.LoadDevices(_executionLog.Object); Assert.Equal(2, calls); Assert.NotEmpty(_simulatorLoader.AvailableDevices); await _simulatorLoader.LoadDevices(_executionLog.Object); Assert.Equal(2, calls); await _simulatorLoader.LoadDevices(_executionLog.Object); Assert.Equal(2, calls); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Hardware/TCCDatabaseTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Hardware; public class TCCDatabaseTests { private readonly Mock _processManager; private readonly TCCDatabase _database; private readonly Mock _executionLog; private readonly string _simRuntime; private readonly string _dataPath; private readonly string _udid; public TCCDatabaseTests() { _processManager = new Mock(); _database = new TCCDatabase(_processManager.Object); _executionLog = new Mock(); _simRuntime = "com.apple.CoreSimulator.SimRuntime.iOS-12-1"; _dataPath = "/path/to/my/data"; _udid = "D9DCBED1EC414ECE9A2353364C2AC454"; } [Theory] [InlineData("com.apple.CoreSimulator.SimRuntime.iOS-12-1", 3)] [InlineData("com.apple.CoreSimulator.SimRuntime.iOS-10-1", 2)] [InlineData("com.apple.CoreSimulator.SimRuntime.iOS-9-1", 2)] [InlineData("com.apple.CoreSimulator.SimRuntime.iOS-7-1", 1)] [InlineData("com.apple.CoreSimulator.SimRuntime.tvOS-12-3", 3)] [InlineData("com.apple.CoreSimulator.SimRuntime.tvOS-8-1", 2)] [InlineData("com.apple.CoreSimulator.SimRuntime.watchOS-5-1", 3)] [InlineData("com.apple.CoreSimulator.SimRuntime.watchOS-4-1", 2)] [InlineData("com.apple.CoreSimulator.SimRuntime.iOS-14-0", 4)] [InlineData("com.apple.CoreSimulator.SimRuntime.tvOS-14-0", 4)] [InlineData("com.apple.CoreSimulator.SimRuntime.watchOS-7-0", 4)] public void GetTCCFormatTest(string runtime, int expected) => Assert.Equal(expected, _database.GetTCCFormat(runtime)); [Fact] public void GetTCCFormatUnknownTest() => Assert.Throws(() => _database.GetTCCFormat("unknown-sim-runtime")); [Fact] public async Task AgreeToPromptsAsyncNoIdentifiers() { // we should write in the log that we did not managed to agree to it _executionLog.Setup(l => l.WriteLine(It.IsAny())); await _database.AgreeToPromptsAsync(_simRuntime, _dataPath, _udid, _executionLog.Object); _executionLog.Verify(l => l.WriteLine("No bundle identifiers given when requested permission editing.")); } [Fact] public async Task AgreeToPropmtsAsyncTimeoutsTest() { string processName = null; // set the process manager to always return a failure so that we do eventually get a timeout _processManager.Setup(p => p.ExecuteCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), null, null)) .Returns, ILog, TimeSpan, Dictionary, CancellationToken?>((p, a, l, t, e, c) => { processName = p; return Task.FromResult(new ProcessExecutionResult { ExitCode = 1, TimedOut = true }); }); // try to accept and fail because we always timeout await _database.AgreeToPromptsAsync(_simRuntime, _dataPath, _udid, _executionLog.Object, "my-bundle-id", "your-bundle-id"); // verify that we did write in the logs and that we did call sqlite3 Assert.Equal("sqlite3", processName); _executionLog.Verify(l => l.WriteLine("Failed to edit TCC.db, the test run might hang due to permission request dialogs"), Times.AtLeastOnce); } [Theory] [InlineData("com.apple.CoreSimulator.SimRuntime.iOS-12-1", 3)] [InlineData("com.apple.CoreSimulator.SimRuntime.iOS-10-1", 2)] [InlineData("com.apple.CoreSimulator.SimRuntime.iOS-7-1", 1)] public async Task AgreeToPromptsAsyncSuccessTest(string runtime, int dbVersion) { string bundleIdentifier = "my-bundle-identifier"; var services = new string[] { "kTCCServiceAll", "kTCCServiceAddressBook", "kTCCServiceCalendar", "kTCCServiceCamera", "kTCCServicePhotos", "kTCCServiceMediaLibrary", "kTCCServiceMicrophone", "kTCCServiceUbiquity", "kTCCServiceWillow" }; var expectedArgs = new StringBuilder("\n"); // assert the sql used depending on the version foreach (var id in new[] { bundleIdentifier, bundleIdentifier + ".watchkitapp" }) { switch (dbVersion) { case 1: foreach (var s in services) { expectedArgs.AppendFormat("DELETE FROM access WHERE service = '{0}' AND client = '{1}';\n", s, id); expectedArgs.AppendFormat("INSERT INTO access VALUES('{0}','{1}',0,1,0,NULL);\n", s, id); } break; case 2: foreach (var s in services) { expectedArgs.AppendFormat("DELETE FROM access WHERE service = '{0}' AND client = '{1}';\n", s, id); expectedArgs.AppendFormat("INSERT INTO access VALUES('{0}','{1}',0,1,0,NULL,NULL);\n", s, id); } break; case 3: foreach (var s in services) { expectedArgs.AppendFormat("INSERT OR REPLACE INTO access VALUES('{0}','{1}',0,1,0,NULL,NULL,NULL,'UNUSED',NULL,NULL,{2});\n", s, id, DateTimeOffset.Now.ToUnixTimeSeconds()); } break; } } string processName = null; IList args = new List(); _processManager.Setup(p => p.ExecuteCommandAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), null, null)) .Returns, ILog, TimeSpan, Dictionary, CancellationToken?>((p, a, l, t, e, c) => { processName = p; args = a; return Task.FromResult(new ProcessExecutionResult { ExitCode = 0, TimedOut = false }); }); await _database.AgreeToPromptsAsync(runtime, _dataPath, _udid, _executionLog.Object, bundleIdentifier); Assert.Equal("sqlite3", processName); // assert that the sql is present Assert.Contains(_dataPath, args); Assert.Contains(expectedArgs.ToString(), args); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Listeners/SimpleFileListenerTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Threading; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Listeners; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Listeners; public class SimpleFileListenerTest : IDisposable { private readonly string _path; private readonly Mock _testLog; private readonly Mock _log; public SimpleFileListenerTest() { _path = Path.GetTempFileName(); _testLog = new Mock(); _log = new Mock(); try { File.Delete(_path); } finally { } } public void Dispose() { if (File.Exists(_path)) { try { File.Delete(_path); } finally { } } GC.SuppressFinalize(this); } [Fact] public void ConstructorNullPathTest() => Assert.Throws(() => new SimpleFileListener(null, _log.Object, _testLog.Object, false)); [Theory] [InlineData("Tests run: ", false)] [InlineData("", true)] public void FileContentIsCopied(string endLine, bool isXml) { var lines = new[] { "first line", "second line", "last line" }; // Create a listener, set the writer and ensure that what we write in the file is present in the final path using (var sourceWriter = new StreamWriter(_path)) { using var listener = new SimpleFileListener(_path, _log.Object, _testLog.Object, isXml); listener.InitializeAndGetPort(); listener.StartAsync(); // Write a number of lines and ensure that those are called in the mock sourceWriter.WriteLine("[Runner executing:"); foreach (var line in lines) { sourceWriter.WriteLine(line); } sourceWriter.WriteLine(endLine); sourceWriter.Flush(); } Thread.Sleep(200); // Verify that the expected lines were added foreach (var line in lines) { _testLog.Verify(l => l.WriteLine(It.Is(ll => ll.Trim() == line.Trim())), Times.AtLeastOnce); } _log.Verify(l => l.WriteLine(It.Is(ll => ll == "Tests have finished executing")), Times.AtLeastOnce); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Listeners/SimpleListenerFactoryTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Listeners; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Listeners; public class SimpleListenerFactoryTest { private readonly Mock _log; private readonly SimpleListenerFactory _factory; public SimpleListenerFactoryTest() { _log = new Mock(); _factory = new SimpleListenerFactory(); } [Fact] public void ConstructorAllowsNullTunnelBore() { _ = new SimpleListenerFactory(null); // if it throws, test fails ;) } [Fact] public void CreateNotWatchListener() { var (transport, listener, listenerTmpFile) = _factory.Create(RunMode.iOS, _log.Object, _log.Object, true, true, true); Assert.Equal(ListenerTransport.Tcp, transport); Assert.IsType(listener); Assert.Null(listenerTmpFile); } [Fact] public void CreateWatchOSSimulator() { var logFullPath = "myfullpath.txt"; _ = _log.Setup(l => l.FullPath).Returns(logFullPath); var (transport, listener, listenerTmpFile) = _factory.Create(RunMode.WatchOS, _log.Object, _log.Object, true, true, true); Assert.Equal(ListenerTransport.File, transport); Assert.IsType(listener); Assert.NotNull(listenerTmpFile); Assert.Equal(logFullPath + ".tmp", listenerTmpFile); _log.Verify(l => l.FullPath, Times.Once); } [Fact] public void CreateWatchOSDevice() { var (transport, listener, listenerTmpFile) = _factory.Create(RunMode.WatchOS, _log.Object, _log.Object, false, true, true); Assert.Equal(ListenerTransport.Http, transport); Assert.IsType(listener); Assert.Null(listenerTmpFile); } [Fact] public void UseTcpTunnel() { var f = new SimpleListenerFactory(null); Assert.False(f.UseTunnel, "Do not use tunnel."); f = new SimpleListenerFactory(Mock.Of()); Assert.True(f.UseTunnel, "Use tunnel."); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Listeners/SimpleTcpListenerTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.IO; using System.Net.Sockets; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Listeners; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Listeners; public class SimpleTcpListenerTest { private readonly Mock _log; private readonly Mock _testLog; public SimpleTcpListenerTest() { _log = new Mock(); _testLog = new Mock(); } [Fact(Skip = "Test is flaky - https://github.com/dotnet/xharness/issues/52")] public void ContentIsSentOverTcp() { var tempResult = Path.GetTempFileName(); // create a stream to be used and write the data there var lines = new string[] { "first line", "second line", "last line" }; // setup the expected data to be written _testLog.Setup(l => l.Write(It.IsAny(), 0, It.IsAny())).Callback((buffer, start, end) => { using (var resultStream = File.Create(tempResult)) {// opening closing a lot, but for the test we do not care resultStream.Write(buffer, start, end); resultStream.Flush(); } }); // create a linstener that will start in an other thread, connect to it // and send the data. var listener = new SimpleTcpListener(_log.Object, _testLog.Object, true, true); listener.InitializeAndGetPort(); var connectionPort = listener.Port; listener.StartAsync(); // create a tcp client which will write the logs, then verity that // the expected data was provided var client = new TcpClient(); client.Connect("localhost", connectionPort); using (var networkStream = client.GetStream()) using (var streamWriter = new StreamWriter(networkStream)) { foreach (var line in lines) { streamWriter.WriteLine(line); streamWriter.Flush(); } } listener.Cancel(); bool firstLineFound = false; bool secondLineFound = false; bool lastLineFound = false; // read the data in the tempResult and ensure lines are present using (var reader = new StreamReader(tempResult)) { string line; while ((line = reader.ReadLine()) != null) { if (line.EndsWith(lines[0])) { firstLineFound = true; } if (line.EndsWith(lines[1])) { secondLineFound = true; } if (line.EndsWith(lines[2])) { lastLineFound = true; } } } Assert.True(firstLineFound, "first line"); Assert.True(secondLineFound, "second line"); Assert.True(lastLineFound, "last line"); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Logging/CaptureLogTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Logging; public class CaptureLogTest : IDisposable { private readonly string _sourcePath; private readonly string _destinationPath; public CaptureLogTest() { _sourcePath = Path.GetTempFileName(); _destinationPath = Path.GetTempFileName(); File.Delete(_sourcePath); File.Delete(_destinationPath); } public void Dispose() { if (File.Exists(_sourcePath)) { File.Delete(_sourcePath); } GC.SuppressFinalize(this); } [Fact] public void ConstructorNullFilePath() { Assert.Throws(() => { var captureLog = new CaptureLog(null, _sourcePath, false); }); } [Fact] public void CapturePartOfTheFileOnly() { var ignoredLine = "This line should not be captured"; var logLines = new[] { "first line", "second line", "third line" }; File.WriteAllLines(_sourcePath, new[] { ignoredLine }); using var captureLog = new CaptureLog(_destinationPath, _sourcePath, false); captureLog.StartCapture(); File.AppendAllLines(_sourcePath, logLines); captureLog.StopCapture(); File.AppendAllLines(_sourcePath, new[] { ignoredLine }); // get the stream and assert we do have the correct lines using var captureStream = captureLog.GetReader(); string logLine; while ((logLine = captureStream.ReadLine()) != null) { Assert.NotEqual(ignoredLine, logLine); if (!string.IsNullOrEmpty(logLine)) { Assert.Contains(logLine, logLines); } } } [Fact] public void CapturePieceByPiece() { var ignoredLine = "This line should not be captured"; var logLines = new[] { "first line", "second line", "third line" }; File.WriteAllLines(_sourcePath, new[] { ignoredLine }); using var captureLog = new CaptureLog(_destinationPath, _sourcePath, false); captureLog.StartCapture(); File.AppendAllLines(_destinationPath, logLines.Take(1)); captureLog.Flush(); Assert.Contains(logLines.First(), File.ReadAllText(_destinationPath)); File.AppendAllLines(_destinationPath, logLines.Skip(1)); captureLog.StopCapture(); // Get the stream and assert we do have the correct lines using var captureStream = captureLog.GetReader(); string logLine; while ((logLine = captureStream.ReadLine()) != null) { Assert.NotEqual(ignoredLine, logLine); if (!string.IsNullOrEmpty(logLine)) { Assert.Contains(logLine, logLines); } } } [Fact] public void CaptureMissingFileTest() { using (var captureLog = new CaptureLog(_destinationPath, _sourcePath, false)) { Assert.Equal(_destinationPath, captureLog.FullPath); captureLog.StartCapture(); captureLog.StopCapture(); } // Read the data that was added to the capture path and ensure that we do have the name of the missing file using (var reader = new StreamReader(_destinationPath)) { var line = reader.ReadLine(); Assert.Contains(_sourcePath, line); } } [Fact] public void CaptureWrongOrder() { Assert.Throws(() => { using var captureLog = new CaptureLog(_destinationPath, _sourcePath, false); captureLog.StopCapture(); }); } [Fact] public void CaptureEverythingAtOnce() { var logLines = new[] { "first line", "second line", "third line" }; File.WriteAllText(_sourcePath, string.Empty); using var captureLog = new CaptureLog(_destinationPath, _sourcePath, false); captureLog.StartCapture(); File.AppendAllLines(_sourcePath, logLines); captureLog.StopCapture(); // get the stream and assert we do have the correct lines using var captureStream = captureLog.GetReader(); string logLine; while ((logLine = captureStream.ReadLine()) != null) { if (!string.IsNullOrEmpty(logLine)) { Assert.Contains(logLine, logLines); } } } [Fact] public void CaptureEntireFile() { var ignoredLine = "This line should not be captured"; var logLines = new List() { "first line", "second line", "third line" }; File.WriteAllLines(_sourcePath, new[] { ignoredLine }); using var captureLog = new CaptureLog(_destinationPath, _sourcePath, true); captureLog.StartCapture(); File.AppendAllLines(_destinationPath, logLines.Take(1)); captureLog.Flush(); Assert.Contains(logLines.First(), File.ReadAllText(_destinationPath)); File.AppendAllLines(_destinationPath, logLines.Skip(1)); captureLog.StopCapture(); // Get the stream and assert we do have the correct lines using var captureStream = captureLog.GetReader(); string logLine; logLines.Add(ignoredLine); while ((logLine = captureStream.ReadLine()) != null) { if (!string.IsNullOrEmpty(logLine)) { Assert.Contains(logLine, logLines); } } } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Logging/LogFileTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Logging; public class LogFileTest : IDisposable { private readonly string _path; private readonly string _description; public LogFileTest() { _description = "My log"; _path = Path.GetTempFileName(); } public void Dispose() { if (File.Exists(_path)) { File.Delete(_path); } GC.SuppressFinalize(this); } [Fact] public void ConstructorTest() { using (var log = new LogFile(_description, _path)) { Assert.Equal(_description, log.Description); Assert.Equal(_path, log.FullPath); } } [Fact] public void ConstructorNullPathTest() => Assert.Throws(() => { var log = new LogFile(_description, null); }); [Fact] public void ConstructorNullDescriptionTest() { using var log = new LogFile(null, _path); } [Fact] public void WriteTest() { const string oldLine = "Hello world!"; const string newLine = "Hola mundo!"; // create a log, write to it and assert that we have the expected data File.WriteAllLines(_path, new[] { oldLine }); using (var log = new LogFile(_description, _path)) { log.WriteLine(newLine); log.Flush(); } bool oldLineFound = false; bool newLineFound = false; using (var reader = new StreamReader(_path)) { string line; while ((line = reader.ReadLine()) != null) { if (line == oldLine) { oldLineFound = true; } if (line.EndsWith(newLine)) // consider time stamp { newLineFound = true; } } } Assert.True(oldLineFound, "old line"); Assert.True(newLineFound, "new line"); } [Fact] public void WriteNotAppendTest() { const string oldLine = "Hello world!"; const string newLine = "Hola mundo!"; // create a log, write to it and assert that we have the expected data File.WriteAllLines(_path, new[] { oldLine }); using (var log = new LogFile(_description, _path, false)) { log.WriteLine(newLine); log.Flush(); } bool oldLineFound = false; bool newLineFound = false; using (var reader = new StreamReader(_path)) { string line; while ((line = reader.ReadLine()) != null) { if (line == oldLine) { oldLineFound = true; } if (line.EndsWith(newLine)) // consider timestamp { newLineFound = true; } } } Assert.False(oldLineFound, "old line"); Assert.True(newLineFound, "new line"); } [Fact] public void WriteNullTest() { using (var log = new LogFile(_description, _path)) { log.Timestamp = false; log.Write(null); log.WriteLine(null); log.Flush(); } Assert.Equal("\n", File.ReadAllText(_path)); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Logging/LogsTest.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Logging; public class LogsTest : IDisposable { private readonly string _directory; private string _fileName; private readonly string _description; public LogsTest() { _directory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); _fileName = "test-file.txt"; _description = "My description"; Directory.CreateDirectory(_directory); } public void Dispose() { if (Directory.Exists(_directory)) { Directory.Delete(_directory, true); } GC.SuppressFinalize(this); } [Fact] public void ConstructorTest() { using (var logs = new Logs(_directory)) { Assert.Equal(_directory, logs.Directory); } } [Fact] public void ConstructorNullDirTest() => Assert.Throws(() => new Logs(null)); [Fact] public void CreateFileTest() { using (var logs = new Logs(_directory)) { var file = logs.CreateFile(_fileName, _description); Assert.True(File.Exists(file), "exists"); Assert.Equal(_fileName, Path.GetFileName(file)); Assert.Single(logs); } } [Fact] public void CreateFileNullPathTest() { using (var logs = new Logs(_directory)) { _fileName = null; var description = "My description"; Assert.Throws(() => logs.CreateFile(_fileName, description)); } } [Fact] public void CreateFileNullDescriptionTest() { using (var logs = new Logs(_directory)) { string description = null; logs.CreateFile(_fileName, description); Assert.Single(logs); } } [Fact] public void AddFileTest() { var fullPath = Path.Combine(_directory, _fileName); File.WriteAllText(fullPath, "foo"); using (var logs = new Logs(_directory)) { var fileLog = logs.AddFile(fullPath, _description); Assert.Equal(fullPath, fileLog.FullPath); // path && fullPath are the same Assert.Equal(Path.Combine(_directory, _fileName), fileLog.FullPath); Assert.Equal(_description, fileLog.Description); } } [Fact] public void AddFileNotInDirTest() { var dir1 = Path.Combine(_directory, "dir1"); var dir2 = Path.Combine(_directory, "dir2"); Directory.CreateDirectory(dir1); Directory.CreateDirectory(dir2); var filePath = Path.Combine(dir1, "test-file.txt"); File.WriteAllText(filePath, "Hello world!"); using (var logs = new Logs(dir2)) { var newPath = Path.Combine(dir2, Path.GetFileNameWithoutExtension(_fileName)); var fileLog = logs.AddFile(filePath, _description); Assert.StartsWith(newPath, fileLog.FullPath); // assert new path Assert.True(File.Exists(fileLog.FullPath), "copy"); } } [Fact] public void AddFilePathNullTest() { using (var logs = new Logs(_directory)) { Assert.Throws(() => logs.AddFile(null, _description)); } } [Fact] public void AddFileDescriptionNull() { var fullPath = Path.Combine(_directory, _fileName); File.WriteAllText(fullPath, "foo"); using (var logs = new Logs(_directory)) { logs.Create(fullPath, null); Assert.Single(logs); } } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests.csproj ================================================ $(NetCurrent) disable ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/ResultFileHandlerTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests; public class ResultFileHandlerTests : IDisposable { private readonly string _tempFile; public ResultFileHandlerTests() { _tempFile = Path.GetTempFileName(); } public void Dispose() { if (File.Exists(_tempFile)) { File.Delete(_tempFile); } } private static ResultFileHandler CreateHandler( Mock processManagerMock, Mock logMock, int[] retryDelaysMs = null) { // Default to no retry delays in tests to keep them fast return new ResultFileHandler(processManagerMock.Object, logMock.Object, retryDelaysMs ?? Array.Empty()); } [Fact] public async Task SimulatorBadOsVersionFormatThrowsException() { Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log); var exception = await Assert.ThrowsAsync(async () => await handler.CopyResultsAsync( RunMode.iOS, true, "Simulator", "udid", "bundle", _tempFile)); Assert.Equal("Simulator OS version is not in the expected format.", exception.Message); } [Fact] public async Task SimulatorBadOsVersionNumberThrowsException() { Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log); var exception = await Assert.ThrowsAsync(async () => await handler.CopyResultsAsync( RunMode.iOS, true, "Simulator notanumber", "udid", "bundle", _tempFile)); Assert.Equal("Simulator OS version is not in the expected format.", exception.Message); } [Fact] public async Task SimulatorOsVersionLessThan18ReturnsFalse() { Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log); bool result = await handler.CopyResultsAsync( RunMode.iOS, true, "Simulator 17.4", "udid", "bundle", _tempFile); Assert.True(result); } [Fact] public async Task SimulatorOsVersion18FileExistsReturnsTrue() { Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log); File.WriteAllText(_tempFile, "dummy"); bool result = await handler.CopyResultsAsync( RunMode.iOS, true, "Simulator 18.0", "udid", "bundle", _tempFile); Assert.True(result); } [Fact] public async Task SimulatorOsVersion18FileMissingReturnsFalse() { Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log); if (File.Exists(_tempFile)) File.Delete(_tempFile); bool result = await handler.CopyResultsAsync( RunMode.iOS, true, "Simulator 18.0", "udid", "bundle", _tempFile); Assert.False(result); log.Verify(l => l.WriteLine($"Failed to copy results file from simulator (attempt 1). Expected at: {_tempFile}"), Times.Once); } [Fact] public async Task DeviceBadOsVersionFormatThrowsException() { Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log); var exception = await Assert.ThrowsAsync(async () => await handler.CopyResultsAsync( RunMode.iOS, false, "notanumber", "udid", "bundle", _tempFile)); Assert.Equal("Device OS version is not in the expected format.", exception.Message); } [Fact] public async Task DeviceOsVersionLessThan18ReturnsTrue() { Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log); bool result = await handler.CopyResultsAsync( RunMode.iOS, false, "17.4", "udid", "bundle", _tempFile); Assert.True(result); } [Fact] public async Task DeviceOsVersion18FileExistsReturnsTrue() { Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log); File.WriteAllText(_tempFile, "dummy"); bool result = await handler.CopyResultsAsync( RunMode.iOS, false, "18.0", "udid", "bundle", _tempFile); Assert.True(result); } [Fact] public async Task DeviceOsVersion18FileMissingReturnsFalse() { Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log); if (File.Exists(_tempFile)) File.Delete(_tempFile); bool result = await handler.CopyResultsAsync( RunMode.iOS, false, "18.0", "udid", "bundle", _tempFile); Assert.False(result); log.Verify(l => l.WriteLine($"Failed to copy results file from device (attempt 1). Expected at: {_tempFile}"), Times.Once); } [Fact] public async Task CopyCrashReportUsesHelixUploadRootWhenAvailable() { // Skip on Windows as mlaunch is not available if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return; } string originalUploadRoot = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT"); string uploadRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(uploadRoot); try { Environment.SetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT", uploadRoot); Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log); string crashReportName = "MyApp-2025-11-25-223847.ips"; string expectedDownloadPath = Path.Combine(uploadRoot, crashReportName); string crashContent = "Dummy crash content"; string actualDownloadPath = null; int callCount = 0; pm.Setup(m => m.ExecuteCommandAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns((MlaunchArguments args, ILog _, TimeSpan _, Dictionary _, int _, CancellationToken? _) => { callCount++; if (callCount == 1) { string listFilePath = GetArgumentValue(args, "list-crash-reports"); File.WriteAllLines(listFilePath, new[] { crashReportName }); } else if (callCount == 2) { actualDownloadPath = GetArgumentValue(args, "download-crash-report-to"); File.WriteAllText(actualDownloadPath, crashContent); } return Task.FromResult(new ProcessExecutionResult { ExitCode = 0 }); }); var appInfo = new AppBundleInformation("MyApp", "com.example.myapp", "/tmp", "/tmp", supports32b: false); await handler.CopyCrashReportAsync("device-udid", null, appInfo, log.Object, isSimulator: false); Assert.Equal(expectedDownloadPath, actualDownloadPath); Assert.True(File.Exists(expectedDownloadPath)); log.Verify(l => l.WriteLine("Attempting to retrieve crash report from device..."), Times.Once); log.Verify(l => l.WriteLine($"Found crash report: {crashReportName}"), Times.Once); log.Verify(l => l.WriteLine("==================== Crash report ===================="), Times.Once); log.Verify(l => l.WriteLine($"Crash report file: {expectedDownloadPath}"), Times.Once); log.Verify(l => l.WriteLine(crashContent), Times.Once); log.Verify(l => l.WriteLine("==================== End of Crash report ===================="), Times.Once); } finally { Environment.SetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT", originalUploadRoot); if (Directory.Exists(uploadRoot)) { Directory.Delete(uploadRoot, true); } } } [Fact] public async Task CopyResultsAsync_WhenFirstAttemptFailsAndSecondSucceeds_ReturnsTrue() { Mock pm = new Mock(); Mock log = new Mock(); // Use a short delay for the test ResultFileHandler handler = CreateHandler(pm, log, new[] { 1 }); if (File.Exists(_tempFile)) File.Delete(_tempFile); int callCount = 0; pm.Setup(m => m.ExecuteCommandAsync( It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .Returns(() => { callCount++; if (callCount == 2) { // Simulate success on second attempt by writing the file File.WriteAllText(_tempFile, "results"); } return Task.FromResult(new ProcessExecutionResult { ExitCode = 0 }); }); bool result = await handler.CopyResultsAsync( RunMode.iOS, false, "18.0", "udid", "bundle", _tempFile); Assert.True(result); Assert.Equal(2, callCount); log.Verify(l => l.WriteLine(It.Is(s => s.Contains("Retrying results file copy (attempt 2)"))), Times.Once); } [Fact] public async Task CopyResultsAsync_WhenAllRetriesFail_ReturnsFalse() { Mock pm = new Mock(); Mock log = new Mock(); // Two retries with minimal delay ResultFileHandler handler = CreateHandler(pm, log, new[] { 1, 1 }); if (File.Exists(_tempFile)) File.Delete(_tempFile); int callCount = 0; pm.Setup(m => m.ExecuteCommandAsync( It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .Returns(() => { callCount++; return Task.FromResult(new ProcessExecutionResult { ExitCode = 1 }); }); bool result = await handler.CopyResultsAsync( RunMode.iOS, false, "18.0", "udid", "bundle", _tempFile); Assert.False(result); // 1 initial attempt + 2 retries = 3 total Assert.Equal(3, callCount); log.Verify(l => l.WriteLine(It.Is(s => s.Contains("Retrying results file copy (attempt 2)"))), Times.Once); log.Verify(l => l.WriteLine(It.Is(s => s.Contains("Retrying results file copy (attempt 3)"))), Times.Once); } [Fact] public async Task CopyCoverageResultsAsync_WhenFirstAttemptFailsAndSecondSucceeds_ReturnsTrue() { Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log, new[] { 1 }); if (File.Exists(_tempFile)) File.Delete(_tempFile); int callCount = 0; pm.Setup(m => m.ExecuteCommandAsync( It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .Returns(() => { callCount++; if (callCount == 2) { File.WriteAllText(_tempFile, "coverage"); } return Task.FromResult(new ProcessExecutionResult { ExitCode = 0 }); }); bool result = await handler.CopyCoverageResultsAsync( RunMode.iOS, false, "18.0", "udid", "bundle", "coverage.cobertura.xml", _tempFile); Assert.True(result); Assert.Equal(2, callCount); log.Verify(l => l.WriteLine(It.Is(s => s.Contains("Retrying coverage results file copy (attempt 2)"))), Times.Once); } [Fact] public async Task CopyCoverageResultsAsync_MacCatalystUsesLocalContainerPath() { Mock pm = new Mock(); Mock log = new Mock(); ResultFileHandler handler = CreateHandler(pm, log); string command = null; pm.Setup(m => m.ExecuteCommandAsync( It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .Returns((string _, IList args, ILog _, ILog _, ILog _, TimeSpan _, Dictionary _, CancellationToken? _) => { command = args[1]; File.WriteAllText(_tempFile, "coverage"); return Task.FromResult(new ProcessExecutionResult { ExitCode = 0 }); }); bool result = await handler.CopyCoverageResultsAsync( RunMode.MacOS, false, Environment.OSVersion.Version.ToString(), string.Empty, "com.example.maccatalyst", "coverage.cobertura.xml", _tempFile); Assert.True(result); Assert.Contains("Library", command); Assert.Contains("Containers", command); Assert.Contains("com.example.maccatalyst", command); Assert.Contains("Documents", command); Assert.Contains("coverage.cobertura.xml", command); } private static string GetArgumentValue(MlaunchArguments args, string argumentName) { string prefix = $"--{argumentName}="; string argument = args.Select(a => a.AsCommandLineArgument()) .First(a => a.StartsWith(prefix, StringComparison.Ordinal)); return argument.Substring(prefix.Length).Trim('"'); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/Info.plist ================================================ CFBundleName com.xamarin.bcltests.SystemXunit CFBundleIdentifier com.xamarin.bcltests.SystemXunit CFBundleShortVersionString 1.0 CFBundleVersion 1.0 LSRequiresIPhoneOS MinimumOSVersion 7.0 UIDeviceFamily 1 2 UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight XSAppIconAssets Assets.xcassets/AppIcon.appiconset NSAppTransportSecurity NSAllowsArbitraryLoads ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/Issue8214.xml ================================================  \r\n504 Gateway Time-out\r\n /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/test-tvos-20200327_154940.xml /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/stderr-20200327_154940.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/run-F73156BF-37D3-4D4A-AC66-DB2103BB1579-20200327_154926.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/test-tvos-20200327_154940.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/stdout-20200327_154940.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/Apple TV.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/3/build-tvOS-20200327_151625.binlog /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/3/build-tvOS-20200327_151625.txt /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/test-tvos-20200327_154940.xml /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/stderr-20200327_154940.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/run-F73156BF-37D3-4D4A-AC66-DB2103BB1579-20200327_154926.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/test-tvos-20200327_154940.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/stdout-20200327_154940.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/4/Apple TV.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/3/build-tvOS-20200327_151625.binlog /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/monotouch-test/3/build-tvOS-20200327_151625.txt ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/Issue95.xml ================================================ Skipped reason Assert.Equal() Failure\nExpected: 1\nActual: 2 at DummyTestProject.DummyTests.FailingTest() in /Users/egorbo/prj/runtime-3/src/libraries/System.Numerics.Vectors/tests/Vector2Tests.cs:line 16 at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) in /Users/egorbo/prj/runtime-3/src/mono/netcore/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs:line 359 ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/MtouchArchMissingEverywhere.xml ================================================  Debug iPhoneSimulator 8.0.30703 2.0 {839212D5-C25B-4284-AA96-59C3872B8184} {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Exe dontlink dont link Xamarin.iOS obj\$(Platform)\$(Configuration)-unified PackageReference \Users\prvysoky\Documents\xamarin-macios\tests True full False bin\iPhoneSimulator\$(Configuration)-unified DEBUG;MONOTOUCH;$(DefineConstants) prompt 4 None True -v -v none true bin\iPhone\$(Configuration)-unified prompt 4 iPhone Developer -v -v True None MONOTOUCH;$(DefineConstants) {F611ED96-54B5-4975-99BB-12F50AF95936} Touch.Client-iOS ..\..\..\..\external\Touch.Unit\Touch.Client\iOS\Touch.Client-iOS.csproj Info.plist Main.cs AppDelegate.cs DontLinkRegressionTests.cs CommonDontLinkTest.cs LaunchScreen.storyboard Assets.xcassets\AppIcons.appiconset\Contents.json Assets.xcassets\AppIcons.appiconset\Icon-app-60%403x.png BoardingPass.pkpass {FE6EDEE9-ADF6-4F42-BCF2-B68C0A44EC3D} BundledResources ..\..\..\BundledResources\BundledResources.csproj ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/MtouchArchMissingInConfiguration.xml ================================================  Debug iPhoneSimulator 8.0.30703 2.0 {839212D5-C25B-4284-AA96-59C3872B8184} {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Exe dontlink dont link Xamarin.iOS obj\$(Platform)\$(Configuration)-unified PackageReference \Users\prvysoky\Documents\xamarin-macios\tests True full False bin\iPhoneSimulator\$(Configuration)-unified DEBUG;MONOTOUCH;$(DefineConstants) prompt 4 None True -v -v none true bin\iPhone\$(Configuration)-unified prompt 4 iPhone Developer -v -v True None ARM64 MONOTOUCH;$(DefineConstants) {F611ED96-54B5-4975-99BB-12F50AF95936} Touch.Client-iOS ..\..\..\..\external\Touch.Unit\Touch.Client\iOS\Touch.Client-iOS.csproj Info.plist Main.cs AppDelegate.cs DontLinkRegressionTests.cs CommonDontLinkTest.cs LaunchScreen.storyboard Assets.xcassets\AppIcons.appiconset\Contents.json Assets.xcassets\AppIcons.appiconset\Icon-app-60%403x.png BoardingPass.pkpass {FE6EDEE9-ADF6-4F42-BCF2-B68C0A44EC3D} BundledResources ..\..\..\BundledResources\BundledResources.csproj ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/NUnitV2Sample.xml ================================================  ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/NUnitV2SampleFailure.xml ================================================  ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/NUnitV3Sample.xml ================================================  ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/NUnitV3SampleFailure.xml ================================================  ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/NUnitV3SampleFailures.xml ================================================ log.xml ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/NUnitV3SampleParameterizedFailure.xml ================================================ /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/execute-Mac_Modern-20200923_052627.txt /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/main-20200923_052836.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/test-Mac_Modern-20200923_052627-clean.xml /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/test-Mac_Modern-20200923_052627.xml /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/execute-Mac_Modern-20200923_052627.txt /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/main-20200923_052836.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/test-Mac_Modern-20200923_052627-clean.xml /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/test-Mac_Modern-20200923_052627.xml /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/execute-Mac_Modern-20200923_052627.txt /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/main-20200923_052836.log /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/test-Mac_Modern-20200923_052627-clean.xml /Users/builder/jenkins/workspace/xamarin-macios-pr-builder/jenkins-results/tests/xammac-tests/311/test-Mac_Modern-20200923_052627.xml ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/NUnitV3SampleSuccess.xml ================================================  ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/TestCaseFailures.xml ================================================ message stacktrace message stacktrace ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/TestProject/Info.plist ================================================ CFBundleName com.xamarin.bcltests.SystemXunit CFBundleIdentifier com.xamarin.bcltests.SystemXunit CFBundleExecutable SystemXunit.bcltests.xamarin.com CFBundleShortVersionString 1.0 CFBundleVersion 1.0 LSRequiresIPhoneOS MinimumOSVersion 7.0 UIDeviceFamily 1 2 UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight XSAppIconAssets Assets.xcassets/AppIcon.appiconset NSAppTransportSecurity NSAllowsArbitraryLoads ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/TestProject/SystemXunit.csproj ================================================  Debug iPhoneSimulator {3A835432-B054-32FD-07CB-F9A8FFCFB44D} {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Exe com.xamarin.bcltests.SystemXunit com.xamarin.bcltests.SystemXunit 67,168,169,219,414,612,618,649,672 Xamarin.iOS Resources True full False bin DEBUG;NET_1_1;NET_2_0;NET_3_0;NET_3_5;NET_4_0;NET_4_5;NET_2_1;MOBILE;MONOTOUCH;FULL_AOT_RUNTIME;DISABLE_CAS_USE;$(DefineConstants) prompt 4 True None True i386, x86_64 cjk,mideast,other,rare,west True full False bin DEBUG;NET_1_1;NET_2_0;NET_3_0;NET_3_5;NET_4_0;NET_4_5;NET_2_1;MOBILE;MONOTOUCH;FULL_AOT_RUNTIME;DISABLE_CAS_USE;$(DefineConstants) prompt 4 iPhone Developer True True ARMv7, ARM64 cjk,mideast,other,rare,west monotouch\nunitlite.dll monotouch\tests\Xunit.NetCore.Extensions.dll monotouch\tests\xunit.execution.dotnet.dll monotouch\tests\monotouch_System_xunit-test.dll Assets.xcassets\AppIcon.appiconset\Contents.json Assets.xcassets\Contents.json Info.plist Entitlements.plist TestRunner.NUnit\NUnitTestRunner.cs TestRunner.NUnit\ClassOrNamespaceFilter.cs TestRunner.NUnit\TestMethodFilter.cs TestRunner.Core\Extensions.Bool.cs TestRunner.Core\LogWriter.cs TestRunner.Core\MinimumLogLevel.cs TestRunner.Core\TcpTextWriter.cs TestRunner.Core\TestAssemblyInfo.cs TestRunner.Core\TestCompletionStatus.cs TestRunner.Core\TestExecutionState.cs TestRunner.Core\TestFailureInfo.cs TestRunner.Core\TestRunner.cs TestRunner.Core\TestRunSelector.cs TestRunner.Core\TestRunSelectorType.cs TestRunner.xUnit\XUnitFilter.cs TestRunner.xUnit\XUnitFilterType.cs TestRunner.xUnit\XUnitTestRunner.cs ApplicationOptions.cs IgnoreFileParser.cs AppDelegate.cs Main.cs ViewController.cs ViewController.designer.cs ViewController.cs RegisterType.cs TestRunner.xUnit\NUnitXml.xslt TestRunner.xUnit\NUnit3Xml.xslt common-monotouch_System_xunit-test.dll.ignore PreserveNewest nunit-excludes.txt PreserveNewest xunit-excludes.txt PreserveNewest ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/TouchUnitSample.xml ================================================  ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/TouchUnitSample2.xml ================================================  c__DisplayClass25_0.<b__0>d.MoveNext() --- End of stack trace from previous location --- at TestRuntime.TryRunAsync(TimeSpan timeout, Task startTask, Task completionTask, UIImage imageToShow, Exception& exception) at TestRuntime.TryRunAsync(TimeSpan timeout, Func`1 startTask, Exception& exception) at MonoTests.System.Net.Http.MessageHandlerTest.UpdateRequestUriAfterRedirect(Type handlerType) ]]> c__DisplayClass25_0.<b__0>d.MoveNext() at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[MonoTests.System.Net.Http.MessageHandlerTest.<>c__DisplayClass25_0.<b__0>d, monotouchtest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065]].ExecutionContextCallback(Object s) ]]> c__DisplayClass25_0.<b__0>d.MoveNext() --- End of stack trace from previous location --- at TestRuntime.TryRunAsync(TimeSpan timeout, Task startTask, Task completionTask, UIImage imageToShow, Exception& exception) at TestRuntime.TryRunAsync(TimeSpan timeout, Func`1 startTask, Exception& exception) at MonoTests.System.Net.Http.MessageHandlerTest.UpdateRequestUriAfterRedirect(Type handlerType) ]]> c__DisplayClass25_0.<b__0>d.MoveNext() at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[MonoTests.System.Net.Http.MessageHandlerTest.<>c__DisplayClass25_0.<b__0>d, monotouchtest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065]].ExecutionContextCallback(Object s) ]]> c__DisplayClass25_0.<b__0>d.MoveNext() --- End of stack trace from previous location --- at TestRuntime.TryRunAsync(TimeSpan timeout, Task startTask, Task completionTask, UIImage imageToShow, Exception& exception) at TestRuntime.TryRunAsync(TimeSpan timeout, Func`1 startTask, Exception& exception) at MonoTests.System.Net.Http.MessageHandlerTest.UpdateRequestUriAfterRedirect(Type handlerType) [FAIL] UpdateRequestUriAfterRedirect(System.Net.Http.SocketsHttpHandler) : Post RequestUri Expected string length 20 but was 64. Strings differ at index 20. Expected: "https://httpbin.org/" But was: "https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2F" -------------------------------^ Not ignoring test, because not running in CI: Ignored due to http status code 'ServiceUnavailable' at MonoTests.System.Net.Http.MessageHandlerTest.<>c__DisplayClass25_0.<b__0>d.MoveNext() --- End of stack trace from previous location --- at TestRuntime.TryRunAsync(TimeSpan timeout, Task startTask, Task completionTask, UIImage imageToShow, Exception& exception) at TestRuntime.TryRunAsync(TimeSpan timeout, Func`1 startTask, Exception& exception) at MonoTests.System.Net.Http.MessageHandlerTest.UpdateRequestUriAfterRedirect(Type handlerType) MonoTests.System.Net.Http.MessageHandlerTest.UpdateRequestUriAfterRedirect : 1017.9735 ms MonoTests.System.Net.Http.MessageHandlerTest : 301804.8905 ms MonoTests.System.Net.Http : 301804.9016 ms MonoTests.System.Net : 301804.9219 ms MonoTests.System : 301804.933 ms MonoTests : 301805.0052 ms MonoTouchFixtures MonoTouchFixtures.Accessibility MonoTouchFixtures.Accessibility.AXHearingUtilitiesTests [PASS] GetDoesSupportBidirectionalHearing [PASS] GetHearingDeviceEar [PASS] GetHearingDevicePairedUuids MonoTouchFixtures.Accessibility.AXHearingUtilitiesTests : 3.1332 ms MonoTouchFixtures.Accessibility.AXPrefersTests [PASS] HorizontalTextEnabled [PASS] NonBlinkingTextInsertionIndicator MonoTouchFixtures.Accessibility.AXPrefersTests : 1.8107 ms MonoTouchFixtures.Accessibility.AXSettingsTest [PASS] IsAssistiveAccessEnabled [IGNORED] OpenSettingsFeature : This test opens the Settings app (stopping executing of monotouch-test), so it's ignored when running automatically. Run again to actually run it (you'll have to switch back to monotouch-test manually). at MonoTouchFixtures.Accessibility.AXSettingsTest.OpenSettingsFeature() MonoTouchFixtures.Accessibility.AXSettingsTest : 1.2402 ms MonoTouchFixtures.Accessibility : 6.231 ms MonoTouchFixtures.AddressBook MonoTouchFixtures.AddressBook.PersonTest [IGNORED] UpdateAddressLine : The addressbook framework is deprecated in Xcode 15.0 and always returns null at MonoTouchFixtures.AddressBook.PersonTest.Setup() MonoTouchFixtures.AddressBook.PersonTest : 0.3722 ms MonoTouchFixtures.AddressBook : 0.3912 ms MonoTouchFixtures.AdSupport MonoTouchFixtures.AdSupport.IdentifierManagerTest [PASS] SharedManager MonoTouchFixtures.AdSupport.IdentifierManagerTest : 0.8037 ms MonoTouchFixtures.AdSupport : 0.8228 ms MonoTouchFixtures.AudioToolbox MonoTouchFixtures.AudioToolbox.AudioBalanceFadeTest [PASS] GetBalanceFade MonoTouchFixtures.AudioToolbox.AudioBalanceFadeTest : 0.5497 ms MonoTouchFixtures.AudioToolbox.AudioBufferListTest [PASS] Usage MonoTouchFixtures.AudioToolbox.AudioBufferListTest : 1.1369 ms MonoTouchFixtures.AudioToolbox.AudioChannelLayoutTest [PASS] FromAudioChannelBitmap [PASS] FromAudioChannelLayoutTag [PASS] GetChannelMap [PASS] GetMatrixMixMap [PASS] GetNumberOfChannels [PASS] GetTagForChannelLayout [PASS] GetTagsForNumberOfChannels [PASS] Validate MonoTouchFixtures.AudioToolbox.AudioChannelLayoutTest : 1.6087 ms MonoTouchFixtures.AudioToolbox.AudioConverterTest [PASS] Convert MonoTouchFixtures.AudioToolbox.AudioConverterTest.ConvertWithPacketDependencies [IGNORED] ConvertWithPacketDependencies(Apac) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.AudioToolbox.AudioConverterTest.ConvertWithPacketDependencies(AudioFormatType targetType) MonoTouchFixtures.AudioToolbox.AudioConverterTest.ConvertWithPacketDependencies : 0.302 ms [PASS] CreateWithOptions [PASS] Formats [PASS] Prepare [PASS] PrepareWithCallback [IGNORED] Properties : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.AudioToolbox.AudioConverterTest.Properties() MonoTouchFixtures.AudioToolbox.AudioConverterTest : 107.6867 ms MonoTouchFixtures.AudioToolbox.AudioFileGlobalInfoTest [PASS] Properties MonoTouchFixtures.AudioToolbox.AudioFileGlobalInfoTest : 20.2719 ms MonoTouchFixtures.AudioToolbox.AudioFileStreamTest [PASS] CafNoData [PASS] FourCC [PASS] UndocumentedFourCC MonoTouchFixtures.AudioToolbox.AudioFileStreamTest : 0.7762 ms MonoTouchFixtures.AudioToolbox.AudioFileTest [PASS] ApiTest [PASS] ReadTest MonoTouchFixtures.AudioToolbox.AudioFileTest : 2.3401 ms MonoTouchFixtures.AudioToolbox.AudioFormatAvailabilityTest [PASS] GetDecoders [PASS] GetEncoders MonoTouchFixtures.AudioToolbox.AudioFormatAvailabilityTest : 0.1762 ms MonoTouchFixtures.AudioToolbox.AudioFormatTest [PASS] GetFirstPlayableFormat MonoTouchFixtures.AudioToolbox.AudioFormatTest : 0.1937 ms MonoTouchFixtures.AudioToolbox.AudioQueueBufferTest [PASS] Properties MonoTouchFixtures.AudioToolbox.AudioQueueBufferTest : 70.2265 ms MonoTouchFixtures.AudioToolbox.AudioQueueTest [PASS] AllocateBuffer_1 [PASS] AllocateBuffer_2 [IGNORED] InvalidAudioBasicDescription : Requires a hardened runtime entitlement: com.apple.security.device.microphone at TestRuntime.RequestMicrophonePermission(Boolean assert_granted) at MonoTouchFixtures.AudioToolbox.AudioQueueTest.InvalidAudioBasicDescription() [IGNORED] ProcessingTap : Fails on some machines with undefined error code 5 MonoTouchFixtures.AudioToolbox.AudioQueueTest : 1.366 ms MonoTouchFixtures.AudioToolbox.AudioStreamBasicDescriptionTest [PASS] CreateLinearPCM [PASS] GetFormatInfo [PASS] VBR MonoTouchFixtures.AudioToolbox.AudioStreamBasicDescriptionTest : 1.0263 ms MonoTouchFixtures.AudioToolbox.FourCCTests [PASS] AudioFilePropertyTest [PASS] AudioFileStreamPropertyTest [PASS] AudioFormatTypeTest [PASS] AudioQueuePropertyTest [PASS] AudioQueueTimePitchAlgorithmtest [PASS] MusicSequenceFileTypeIDTest [PASS] MusicSequenceTypeTest MonoTouchFixtures.AudioToolbox.FourCCTests : 0.2649 ms MonoTouchFixtures.AudioToolbox.MusicPlayerTest [PASS] CreateTest [PASS] Defaults [PASS] MusicSequenceTest [PASS] PlayRateScalarTest [PASS] StartStopPreroll [PASS] TimeTest MonoTouchFixtures.AudioToolbox.MusicPlayerTest : 2.1812 ms MonoTouchFixtures.AudioToolbox.MusicSequenceTest [PASS] Defaults [PASS] SMPTEResolution MonoTouchFixtures.AudioToolbox.MusicSequenceTest : 0.8498 ms MonoTouchFixtures.AudioToolbox.MusicTrackTest [PASS] Defaults [PASS] MidiEndPointProperty MonoTouchFixtures.AudioToolbox.MusicTrackTest : 595.9241 ms MonoTouchFixtures.AudioToolbox.SoundBankTest [PASS] GetInstrumentInfo [PASS] GetInstrumentInfo_DLS_SimOnly [PASS] GetName [PASS] GetName_DLS_SimOnly MonoTouchFixtures.AudioToolbox.SoundBankTest : 19.7471 ms MonoTouchFixtures.AudioToolbox.SystemSoundTest [PASS] DisposeTest [PASS] FromFile [PASS] Properties [PASS] TestCallbackPlayAlert [PASS] TestCallbackPlaySystem MonoTouchFixtures.AudioToolbox.SystemSoundTest : 1425.5619 ms MonoTouchFixtures.AudioToolbox : 2252.0858 ms MonoTouchFixtures.AudioUnit MonoTouchFixtures.AudioUnit.AUAudioUnitFactoryTest [PASS] CreateAudioUnit MonoTouchFixtures.AudioUnit.AUAudioUnitFactoryTest : 41.9213 ms MonoTouchFixtures.AudioUnit.AudioUnitTest [PASS] CopyIconTest [PASS] DisposeMethodTest [PASS] GetElementCount [PASS] TestSizeOf MonoTouchFixtures.AudioUnit.AudioUnitTest : 2.715 ms MonoTouchFixtures.AudioUnit.AUGraphTest [PASS] BasicOperations [PASS] Connections [PASS] CreateTest [PASS] GetNativeTest MonoTouchFixtures.AudioUnit.AUGraphTest : 6.7322 ms MonoTouchFixtures.AudioUnit.AVSpeechSynthesisProviderAudioUnitTest [PASS] Create MonoTouchFixtures.AudioUnit.AVSpeechSynthesisProviderAudioUnitTest : 1.8147 ms MonoTouchFixtures.AudioUnit.ExtAudioFileTest [PASS] ClientDataFormat [PASS] OpenCFUrlTest [PASS] OpenNSUrlTest [PASS] WrapAudioFileID MonoTouchFixtures.AudioUnit.ExtAudioFileTest : 1.8887 ms MonoTouchFixtures.AudioUnit : 55.1787 ms MonoTouchFixtures.AuthenticationServices MonoTouchFixtures.AuthenticationServices.PublicPrivateKeyAuthenticationTests [PASS] GetAllSupportedPublicKeyCredentialDescriptorTransports MonoTouchFixtures.AuthenticationServices.PublicPrivateKeyAuthenticationTests : 0.8151 ms MonoTouchFixtures.AuthenticationServices : 0.8384 ms MonoTouchFixtures.AVFoundation MonoTouchFixtures.AVFoundation.AudioPlayerTest [PASS] FromData [PASS] FromDataWithHint [PASS] FromDataWithNullData [PASS] FromInvalidUrlWithHint [PASS] FromUrl [PASS] FromUrlWithHint [PASS] FromUrlWithInvalidUrl MonoTouchFixtures.AVFoundation.AudioPlayerTest : 3.7867 ms MonoTouchFixtures.AVFoundation.AudioRecorderTest [IGNORED] Create : Requires a hardened runtime entitlement: com.apple.security.device.microphone at TestRuntime.RequestMicrophonePermission(Boolean assert_granted) at MonoTouchFixtures.AVFoundation.AudioRecorderTest.Create() [IGNORED] CreateWithError : Requires a hardened runtime entitlement: com.apple.security.device.microphone at TestRuntime.RequestMicrophonePermission(Boolean assert_granted) at MonoTouchFixtures.AVFoundation.AudioRecorderTest.CreateWithError() MonoTouchFixtures.AVFoundation.AudioRecorderTest : 0.5285 ms MonoTouchFixtures.AVFoundation.AVAssetImageGeneratorTest [PASS] AppliesPreferredTrackTransform [PASS] CopyCGImageAtTime [PASS] CopyCGImageAtTime_Invalid [PASS] Defaults [PASS] GenerateCGImagesAsynchronously MonoTouchFixtures.AVFoundation.AVAssetImageGeneratorTest : 133.1579 ms MonoTouchFixtures.AVFoundation.AVAudioConverterPrimeInfoTest [PASS] AreEqualDiffType [PASS] AreEqualFalseTest [PASS] AreEqualTrueTest [PASS] ConstructorTest MonoTouchFixtures.AVFoundation.AVAudioConverterPrimeInfoTest : 0.2132 ms MonoTouchFixtures.AVFoundation.AVAudioFormatTest [PASS] StreamDescription [PASS] TestEqualOperatorNull [PASS] TestEqualOperatorSameInstace [PASS] TestNotEqualOperatorNull MonoTouchFixtures.AVFoundation.AVAudioFormatTest : 0.424 ms MonoTouchFixtures.AVFoundation.AVAudioSinkNodeTest [PASS] SinkNodeCallback [PASS] SinkNodeCallback2 [PASS] SinkNodeCallbackRaw MonoTouchFixtures.AVFoundation.AVAudioSinkNodeTest : 126.0402 ms MonoTouchFixtures.AVFoundation.AVAudioSourceNodeTest [PASS] SourceNodeCallback [PASS] SourceNodeCallback2 [PASS] SourceNodeCallbackRaw MonoTouchFixtures.AVFoundation.AVAudioSourceNodeTest : 104.6941 ms MonoTouchFixtures.AVFoundation.AVAudioVoiceProcessingOtherAudioDuckingConfigurationTest [PASS] Properties MonoTouchFixtures.AVFoundation.AVAudioVoiceProcessingOtherAudioDuckingConfigurationTest : 0.1832 ms MonoTouchFixtures.AVFoundation.AVBeatRangeTest [PASS] AreEqualDiffType [PASS] AreEqualFalseTest [PASS] AreEqualTrueTest [PASS] ConstructorTest MonoTouchFixtures.AVFoundation.AVBeatRangeTest : 0.1464 ms MonoTouchFixtures.AVFoundation.AVCaptureReactionTypeTest [PASS] GetSystemImage MonoTouchFixtures.AVFoundation.AVCaptureReactionTypeTest : 0.58 ms MonoTouchFixtures.AVFoundation.AVCaptureTimecodeTests [IGNORED] AddFramesTest : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.AVFoundation.AVCaptureTimecodeTests.Setup() [IGNORED] EqualityOperator_FalseForDifferentValues : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.AVFoundation.AVCaptureTimecodeTests.Setup() [IGNORED] EqualityOperator_TrueForIdenticalValues : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.AVFoundation.AVCaptureTimecodeTests.Setup() [IGNORED] EqualsMethod_FalseForDifferentValues : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.AVFoundation.AVCaptureTimecodeTests.Setup() [IGNORED] EqualsMethod_TrueForIdenticalValues : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.AVFoundation.AVCaptureTimecodeTests.Setup() [IGNORED] GetHashCode_EqualForIdenticalValues : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.AVFoundation.AVCaptureTimecodeTests.Setup() [IGNORED] GetHashCode_NotEqualForDifferentValues : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.AVFoundation.AVCaptureTimecodeTests.Setup() [IGNORED] MetadataSampleBufferTest : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.AVFoundation.AVCaptureTimecodeTests.Setup() MonoTouchFixtures.AVFoundation.AVCaptureTimecodeTests : 0.7274 ms MonoTouchFixtures.AVFoundation.AVCaptureWhiteBalanceGainsTest [PASS] AreEqualDiffType [PASS] AreEqualFalseTest [PASS] AreEqualTrueTest [PASS] ConstructorTest MonoTouchFixtures.AVFoundation.AVCaptureWhiteBalanceGainsTest : 0.1631 ms MonoTouchFixtures.AVFoundation.AVDepthDataTests [IGNORED] AvailableDepthDataTypesTest : This test only runs on device. at MonoTouchFixtures.AVFoundation.AVDepthDataTests.AvailableDepthDataTypesTest() MonoTouchFixtures.AVFoundation.AVDepthDataTests : 0.0905 ms MonoTouchFixtures.AVFoundation.AVFoundationEnumTest [PASS] PromptStyle MonoTouchFixtures.AVFoundation.AVFoundationEnumTest : 0.0391 ms MonoTouchFixtures.AVFoundation.AVSpeechSynthesisMarkerTest [PASS] NSRangeCtor [PASS] StringCtor MonoTouchFixtures.AVFoundation.AVSpeechSynthesisMarkerTest : 0.7552 ms MonoTouchFixtures.AVFoundation.AVSpeechUtteranceTest [PASS] StringCtor [PASS] StringOptionCtor_PlainText [PASS] StringOptionCtor_Ssml MonoTouchFixtures.AVFoundation.AVSpeechUtteranceTest : 1.1517 ms MonoTouchFixtures.AVFoundation.CaptureMetadataOutputTest [PASS] Defaults [PASS] Flags [IGNORED] MetadataObjectTypesTest : This test only runs on device (requires camera access) at TestRuntime.AssertDevice(String message) at MonoTouchFixtures.AVFoundation.CaptureMetadataOutputTest.MetadataObjectTypesTest() MonoTouchFixtures.AVFoundation.CaptureMetadataOutputTest : 6.4302 ms MonoTouchFixtures.AVFoundation.CMTagCollectionVideoOutputPresetTest [PASS] Create MonoTouchFixtures.AVFoundation.CMTagCollectionVideoOutputPresetTest : 0.2495 ms MonoTouchFixtures.AVFoundation.MetadataObjectTest [PASS] Defaults MonoTouchFixtures.AVFoundation.MetadataObjectTest : 0.3501 ms MonoTouchFixtures.AVFoundation.PlayerItemTest [PASS] FromAssert_Null MonoTouchFixtures.AVFoundation.PlayerItemTest : 0.1018 ms MonoTouchFixtures.AVFoundation.PlayerItemVideoOutputTest [PASS] Ctor_CVPixelBufferAttributes MonoTouchFixtures.AVFoundation.PlayerItemVideoOutputTest : 0.3834 ms MonoTouchFixtures.AVFoundation.QueuePlayerTest [PASS] NullAllowedTest MonoTouchFixtures.AVFoundation.QueuePlayerTest : 0.544 ms MonoTouchFixtures.AVFoundation.SpeechSynthesisVoiceTest [PASS] Default [PASS] Static MonoTouchFixtures.AVFoundation.SpeechSynthesisVoiceTest : 65.4449 ms MonoTouchFixtures.AVFoundation.UtilitiesTest [PASS] AspectRatio MonoTouchFixtures.AVFoundation.UtilitiesTest : 0.3288 ms MonoTouchFixtures.AVFoundation.VideoCompositionInstructionTest [PASS] Defaults [PASS] Seven MonoTouchFixtures.AVFoundation.VideoCompositionInstructionTest : 0.5569 ms MonoTouchFixtures.AVFoundation : 447.1972 ms MonoTouchFixtures.BackgroundTasks MonoTouchFixtures.BackgroundTasks.BGTaskSchedulerTest [IGNORED] SubmitTaskRequestTest : This test only runs on device. at MonoTouchFixtures.BackgroundTasks.BGTaskSchedulerTest.SubmitTaskRequestTest() MonoTouchFixtures.BackgroundTasks.BGTaskSchedulerTest : 0.2007 ms MonoTouchFixtures.BackgroundTasks : 0.2246 ms MonoTouchFixtures.CategoryTest [PASS] Instance [IGNORED] NavigationControllerOverride : This test is disabled on Mac Catalyst. at TestRuntime.IgnoreOnMacCatalyst(String message) at MonoTouchFixtures.CategoryTest.NavigationControllerOverride() [PASS] Static MonoTouchFixtures.CategoryTest : 0.6282 ms MonoTouchFixtures.CloudKit MonoTouchFixtures.CloudKit.CKFetchRecordChangesOperationTest [PASS] TestAllChangesReported [PASS] TestRecordChangedSetter [PASS] TestRecordDeletedSetter MonoTouchFixtures.CloudKit.CKFetchRecordChangesOperationTest : 12.0238 ms MonoTouchFixtures.CloudKit.CKFetchRecordsOperationTest [PASS] PerRecordCompletionSetter [PASS] PerRecordProgressSetter [PASS] TestCompletedSetter MonoTouchFixtures.CloudKit.CKFetchRecordsOperationTest : 0.5176 ms MonoTouchFixtures.CloudKit.CKFetchRecordZonesOperationTest [PASS] TestCompletedSetter MonoTouchFixtures.CloudKit.CKFetchRecordZonesOperationTest : 0.2119 ms MonoTouchFixtures.CloudKit.CKFetchSubscriptionsOperationTest [PASS] TestCompletedSetter MonoTouchFixtures.CloudKit.CKFetchSubscriptionsOperationTest : 0.2069 ms MonoTouchFixtures.CloudKit.CKModifyRecordsOperationTest [PASS] Default [PASS] PerRecordCompletionSetter [PASS] PerRecordProgressSetter [PASS] TestCompletedSetter MonoTouchFixtures.CloudKit.CKModifyRecordsOperationTest : 0.857 ms MonoTouchFixtures.CloudKit.CKModifyRecordZonesOperationTest [PASS] Default [PASS] TestCompletedSetter MonoTouchFixtures.CloudKit.CKModifyRecordZonesOperationTest : 0.4376 ms MonoTouchFixtures.CloudKit.CKModifySubscriptionsOperationTest [PASS] TestCompletedSetter MonoTouchFixtures.CloudKit.CKModifySubscriptionsOperationTest : 0.2034 ms MonoTouchFixtures.CloudKit.CKQueryOperationTest [PASS] TestCompletedSetter [PASS] TestRecordFetchedSetter MonoTouchFixtures.CloudKit.CKQueryOperationTest : 0.793 ms MonoTouchFixtures.CloudKit.CKUserIdentityLookupInfoTest [PASS] TestFromEmail [PASS] TestFromPhoneNumber [PASS] TestFromRecordID [PASS] TestGetLookupInfosWithEmails [PASS] TestGetLookupInfosWithPhoneNumbers [PASS] TestGetLookupInfosWithRecordIds MonoTouchFixtures.CloudKit.CKUserIdentityLookupInfoTest : 0.7202 ms MonoTouchFixtures.CloudKit : 15.9921 ms MonoTouchFixtures.Compression MonoTouchFixtures.Compression.DeflateStreamTest [PASS] Bug19313 [PASS] CheckClosedFlush [PASS] CheckClosedRead MonoTouchFixtures.Compression.DeflateStreamTest.CheckCompressDecompress [PASS] CheckCompressDecompress(LZ4) [PASS] CheckCompressDecompress(Lzfse) [PASS] CheckCompressDecompress(Lzma) [PASS] CheckCompressDecompress(Zlib) MonoTouchFixtures.Compression.DeflateStreamTest.CheckCompressDecompress : 4.2067 ms [PASS] CheckCompressingRead [PASS] CheckDecompress [PASS] CheckGetCanReadProp [PASS] CheckGetCanSeekProp [PASS] CheckGetCanWriteProp [PASS] CheckGetLengthProp [PASS] CheckGetPositionProp [PASS] CheckNullRead [PASS] CheckRangeRead [PASS] CheckSeek [PASS] CheckSetLength [PASS] CheckSetLengthProp [PASS] Constructor_InvalidCompressionMode [PASS] Constructor_Null [PASS] DisposeTest MonoTouchFixtures.Compression.DeflateStreamTest.JunkAtTheEnd [PASS] JunkAtTheEnd(LZ4) True [PASS] JunkAtTheEnd(Lzfse) True [PASS] JunkAtTheEnd(Lzma) True [PASS] JunkAtTheEnd(Zlib) True MonoTouchFixtures.Compression.DeflateStreamTest.JunkAtTheEnd : 1.8816 ms MonoTouchFixtures.Compression.DeflateStreamTest : 7.7456 ms MonoTouchFixtures.Compression.ThoroughCompressionStreamTest MonoTouchFixtures.Compression.ThoroughCompressionStreamTest.TestDecodeRealFile [PASS] TestDecodeRealFile(LZ4,"compressed_lz4") [PASS] TestDecodeRealFile(Lzfse,"compressed_lze") [PASS] TestDecodeRealFile(Lzma,"compressed_lzma") [PASS] TestDecodeRealFile(Zlib,"compressed_zip") MonoTouchFixtures.Compression.ThoroughCompressionStreamTest.TestDecodeRealFile : 7.7537 ms MonoTouchFixtures.Compression.ThoroughCompressionStreamTest : 7.8383 ms MonoTouchFixtures.Compression : 15.6057 ms MonoTouchFixtures.ConfigTest [PASS] Existence MonoTouchFixtures.ConfigTest : 1.4028 ms MonoTouchFixtures.Contacts MonoTouchFixtures.Contacts.ContactFetchRequestTest [IGNORED] Ctor_ICNKeyDescriptorArray : This test would show a dialog to ask for permission to access the contacts. at TestRuntime.RequestContactsPermission(Boolean assert_granted) at MonoTouchFixtures.Contacts.ContactFetchRequestTest.MinimumSdkCheck() [IGNORED] Ctor_Mixed : This test would show a dialog to ask for permission to access the contacts. at TestRuntime.RequestContactsPermission(Boolean assert_granted) at MonoTouchFixtures.Contacts.ContactFetchRequestTest.MinimumSdkCheck() [IGNORED] Ctor_NSString : This test would show a dialog to ask for permission to access the contacts. at TestRuntime.RequestContactsPermission(Boolean assert_granted) at MonoTouchFixtures.Contacts.ContactFetchRequestTest.MinimumSdkCheck() MonoTouchFixtures.Contacts.ContactFetchRequestTest : 72.2791 ms MonoTouchFixtures.Contacts.ContactFormatterTest [PASS] GetDescriptorForRequiredKeys_FullName [PASS] GetDescriptorForRequiredKeys_PhoneticFullName MonoTouchFixtures.Contacts.ContactFormatterTest : 1.3375 ms MonoTouchFixtures.Contacts.ContactStoreTest [IGNORED] GetUnifiedContacts : This test would show a dialog to ask for permission to access the contacts. at TestRuntime.CheckContactsPermission(Boolean assert_granted) at MonoTouchFixtures.Contacts.ContactStoreTest.GetUnifiedContacts() MonoTouchFixtures.Contacts.ContactStoreTest : 21.3727 ms MonoTouchFixtures.Contacts.ContactTest [PASS] Ctor [PASS] DescriptorForAllComparatorKeys MonoTouchFixtures.Contacts.ContactTest : 2.244 ms MonoTouchFixtures.Contacts.ContactVCardSerializationTest [IGNORED] GetDescriptorFromRequiredKeys : This test would show a dialog to ask for permission to access the contacts. at TestRuntime.RequestContactsPermission(Boolean assert_granted) at MonoTouchFixtures.Contacts.ContactVCardSerializationTest.MinimumSdkCheck() MonoTouchFixtures.Contacts.ContactVCardSerializationTest : 21.8961 ms MonoTouchFixtures.Contacts.MutableContactTest [PASS] Properties MonoTouchFixtures.Contacts.MutableContactTest : 2.4882 ms MonoTouchFixtures.Contacts : 121.6542 ms MonoTouchFixtures.CoreAnimation MonoTouchFixtures.CoreAnimation.CAFrameRateRangeTest [PASS] DefaultTest [PASS] IsEqualToTest MonoTouchFixtures.CoreAnimation.CAFrameRateRangeTest : 0.1477 ms MonoTouchFixtures.CoreAnimation.CATextLayerTests [PASS] CATextLayerAlignmentModeTest [PASS] CATextLayerTruncationModeTest MonoTouchFixtures.CoreAnimation.CATextLayerTests : 0.5665 ms MonoTouchFixtures.CoreAnimation.EmitterCellTest [PASS] XEmitterCellTest MonoTouchFixtures.CoreAnimation.EmitterCellTest : 0.2506 ms MonoTouchFixtures.CoreAnimation.LayerTest [PASS] AddAnimation [PASS] CAActionTest [PASS] ConvertPoint [PASS] ConvertRect [PASS] ConvertTime [PASS] Mask [PASS] TestBug26532 [PASS] TestCALayerDelegateDispose MonoTouchFixtures.CoreAnimation.LayerTest : 185.0906 ms MonoTouchFixtures.CoreAnimation.ShapeLayerTest [PASS] NullableProperties MonoTouchFixtures.CoreAnimation.ShapeLayerTest : 0.7588 ms MonoTouchFixtures.CoreAnimation.TransactionTest [PASS] AnimationTimingFunction_Null [PASS] CompletionBlock_Null MonoTouchFixtures.CoreAnimation.TransactionTest : 0.17 ms MonoTouchFixtures.CoreAnimation : 187.0054 ms MonoTouchFixtures.CoreAudioKit MonoTouchFixtures.CoreAudioKit.AUViewControllerTest [PASS] Ctor MonoTouchFixtures.CoreAudioKit.AUViewControllerTest : 0.3809 ms MonoTouchFixtures.CoreAudioKit : 0.3902 ms MonoTouchFixtures.CoreBluetooth MonoTouchFixtures.CoreBluetooth.CBPeerTest [PASS] Constructor MonoTouchFixtures.CoreBluetooth.CBPeerTest : 0.0371 ms MonoTouchFixtures.CoreBluetooth.ErrorTest [PASS] AttErrorDomain [PASS] ErrorDomain MonoTouchFixtures.CoreBluetooth.ErrorTest : 0.1876 ms MonoTouchFixtures.CoreBluetooth.PeripheralScanningOptionsTest [PASS] AllowDuplicatesKey_False [PASS] AllowDuplicatesKey_True [PASS] Defaults MonoTouchFixtures.CoreBluetooth.PeripheralScanningOptionsTest : 0.5227 ms MonoTouchFixtures.CoreBluetooth.UuidTest [PASS] Equality_FullRandomEquals [PASS] Equality_FullRandomNotEqual [PASS] Equality_Null [PASS] Equality_PartialEquals [PASS] Equality_PartialEqualsFull [PASS] Equality_PartialsOfDifferentSizeNotEqual [PASS] Roundtrip_128bits [PASS] Roundtrip_16bits MonoTouchFixtures.CoreBluetooth.UuidTest : 1.4089 ms MonoTouchFixtures.CoreBluetooth : 2.1858 ms MonoTouchFixtures.CoreData MonoTouchFixtures.CoreData.AttributeDescription [PASS] DefaultValue [PASS] GetSetRenamingIdentifier [PASS] WeakFramework MonoTouchFixtures.CoreData.AttributeDescription : 0.4203 ms MonoTouchFixtures.CoreData.ExpressionDescriptionTest [PASS] GetSetExpression [PASS] GetSetResultType [PASS] WeakFramework MonoTouchFixtures.CoreData.ExpressionDescriptionTest : 0.3064 ms MonoTouchFixtures.CoreData.FetchedResultsControllerTest [PASS] Default [PASS] PerformFetch_Minimal [PASS] Sections MonoTouchFixtures.CoreData.FetchedResultsControllerTest : 5.5982 ms MonoTouchFixtures.CoreData.FetchRequestExpressionTest [PASS] GetContext [PASS] GetIsCountOnly [PASS] GetRequest [PASS] WeakFramework MonoTouchFixtures.CoreData.FetchRequestExpressionTest : 0.4461 ms MonoTouchFixtures.CoreData.FetchRequestTest [PASS] CtorString [PASS] DefaultValues [PASS] SettersNull MonoTouchFixtures.CoreData.FetchRequestTest : 0.4795 ms MonoTouchFixtures.CoreData.ManagedObjectContextTest [PASS] Default [PASS] Main [PASS] Perform_Null [PASS] PerformAndWait_Null [PASS] UndoManager_Null MonoTouchFixtures.CoreData.ManagedObjectContextTest : 1.1232 ms MonoTouchFixtures.CoreData.ManagedObjectModelTest [PASS] IsConfiguration_Null MonoTouchFixtures.CoreData.ManagedObjectModelTest : 0.23 ms MonoTouchFixtures.CoreData.NSPersistentStoreCoordinatorTest [PASS] GetManagedObjectId MonoTouchFixtures.CoreData.NSPersistentStoreCoordinatorTest : 0.2955 ms MonoTouchFixtures.CoreData.NSQueryGenerationTokenTest [PASS] EncodeWithCoderTest MonoTouchFixtures.CoreData.NSQueryGenerationTokenTest : 0.4513 ms MonoTouchFixtures.CoreData.PropertyDescriptionTest [PASS] GetSetName [PASS] GetSetOpcional [PASS] GetSetRenamingIdentifier [PASS] GetSetTransient [PASS] WeakFramework MonoTouchFixtures.CoreData.PropertyDescriptionTest : 0.3158 ms MonoTouchFixtures.CoreData : 9.7203 ms MonoTouchFixtures.CoreFoundation MonoTouchFixtures.CoreFoundation.ArrayTest [PASS] ArrayFromHandleFuncTest [PASS] ArrayFromHandleFuncTest_bool_false [PASS] ArrayFromHandleFuncTest_bool_true [PASS] ArrayFromHandleTest [PASS] ArrayFromHandleTest_bool_false [PASS] ArrayFromHandleTest_bool_true [PASS] CreateTest [PASS] FromStringsTest MonoTouchFixtures.CoreFoundation.ArrayTest : 0.796 ms MonoTouchFixtures.CoreFoundation.BundleTest [PASS] GetLocalizedString [PASS] GetLocalizedStringWithLanguages [PASS] TestArchitectures MonoTouchFixtures.CoreFoundation.BundleTest.TestAuxiliaryExecutableUrlNull [PASS] TestAuxiliaryExecutableUrlNull("") [PASS] TestAuxiliaryExecutableUrlNull(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestAuxiliaryExecutableUrlNull : 0.1137 ms [PASS] TestBuiltInPlugInsUrl [PASS] TestDevelopmentRegion [PASS] TestExecutableUrl [PASS] TestGetAll [PASS] TestGetAuxiliaryExecutableUrlNull [PASS] TestGetBundleId [PASS] TestGetBundleIdMissing MonoTouchFixtures.CoreFoundation.BundleTest.TestGetBundleIdNull [PASS] TestGetBundleIdNull("") [PASS] TestGetBundleIdNull(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestGetBundleIdNull : 0.1542 ms [PASS] TestGetInfoDictionaryNull [PASS] TestGetLocalizationsForPreferencesNullLocalArray [PASS] TestGetLocalizationsForPreferencesNullPrefArray [PASS] TestGetMain [PASS] TestLocalizations [PASS] TestLocalizationsNull MonoTouchFixtures.CoreFoundation.BundleTest.TestLocalizedStringNullKey [PASS] TestLocalizedStringNullKey("") [PASS] TestLocalizedStringNullKey(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestLocalizedStringNullKey : 0.1118 ms [PASS] TestPreferredLocalizations [PASS] TestPreferredLocalizationsNull [PASS] TestPrivateFrameworksUrl [PASS] TestResourcesDirectoryUrl MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlLocalNameNullLocale [PASS] TestResourceUrlLocalNameNullLocale("") [PASS] TestResourceUrlLocalNameNullLocale(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlLocalNameNullLocale : 0.1646 ms MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlLocalNameNullName [PASS] TestResourceUrlLocalNameNullName("") [PASS] TestResourceUrlLocalNameNullName(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlLocalNameNullName : 0.0617 ms MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlLocalNameNullType [PASS] TestResourceUrlLocalNameNullType("") [PASS] TestResourceUrlLocalNameNullType(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlLocalNameNullType : 0.0955 ms MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlNullName [PASS] TestResourceUrlNullName("") [PASS] TestResourceUrlNullName(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlNullName : 0.0887 ms MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlNullType [PASS] TestResourceUrlNullType("") [PASS] TestResourceUrlNullType(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlNullType : 0.058 ms MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlsLocalNameNullLocale [PASS] TestResourceUrlsLocalNameNullLocale("") [PASS] TestResourceUrlsLocalNameNullLocale(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlsLocalNameNullLocale : 0.0823 ms MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlsLocalNameNullType [PASS] TestResourceUrlsLocalNameNullType("") [PASS] TestResourceUrlsLocalNameNullType(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlsLocalNameNullType : 0.0567 ms MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlsNullType [PASS] TestResourceUrlsNullType("") [PASS] TestResourceUrlsNullType(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestResourceUrlsNullType : 0.063 ms [PASS] TestSharedFrameworksUrl [PASS] TestSharedSupportUrl [PASS] TestStaticResourceUrlNull MonoTouchFixtures.CoreFoundation.BundleTest.TestStaticResourceUrlNullName [PASS] TestStaticResourceUrlNullName("") [PASS] TestStaticResourceUrlNullName(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestStaticResourceUrlNullName : 0.0645 ms MonoTouchFixtures.CoreFoundation.BundleTest.TestStaticResourceUrlNullType [PASS] TestStaticResourceUrlNullType("") [PASS] TestStaticResourceUrlNullType(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestStaticResourceUrlNullType : 0.0597 ms MonoTouchFixtures.CoreFoundation.BundleTest.TestStaticResourceUrlsNullType [PASS] TestStaticResourceUrlsNullType("") [PASS] TestStaticResourceUrlsNullType(null) MonoTouchFixtures.CoreFoundation.BundleTest.TestStaticResourceUrlsNullType : 0.0774 ms [PASS] TestStaticResourceUrlsNullType [PASS] TestSupportFilesDirectoryUrl [PASS] TestUrl MonoTouchFixtures.CoreFoundation.BundleTest : 916.8584 ms MonoTouchFixtures.CoreFoundation.CFNotificationCenterTest [PASS] TestNullNameAndObserver [PASS] TestObservers [PASS] TestObservers2 MonoTouchFixtures.CoreFoundation.CFNotificationCenterTest : 0.9152 ms MonoTouchFixtures.CoreFoundation.CFPropertyListTests [PASS] AsData [PASS] Constructors [PASS] CreateFromData [PASS] DeepCopy [PASS] IsValid [PASS] Value MonoTouchFixtures.CoreFoundation.CFPropertyListTests : 2.3559 ms MonoTouchFixtures.CoreFoundation.CFSocketDataEventArgsTests [PASS] CFData_LazyLoading_WorksCorrectly [PASS] Constructor_WithByteArray_SetsPropertiesCorrectly [PASS] Constructor_WithNullByteArray_AcceptsNull [PASS] Constructor_WithNullRemoteEndPoint_AcceptsNull [PASS] Data_AccessedMultipleTimes_ReturnsSameInstance [PASS] Data_WithEmptyByteArray_ReturnsEmptyArray [PASS] Data_WithEmptyCFData_ReturnsEmptyArray [PASS] InheritsFromEventArgs [PASS] LargeDataArray_HandledCorrectly [PASS] RemoteEndPoint_DifferentPorts_SetsCorrectly [PASS] RemoteEndPoint_IPv6Address_SetsCorrectly MonoTouchFixtures.CoreFoundation.CFSocketDataEventArgsTests : 0.7205 ms MonoTouchFixtures.CoreFoundation.CFSocketTest [PASS] Collected [PASS] RetainCount MonoTouchFixtures.CoreFoundation.CFSocketTest : 202.2324 ms MonoTouchFixtures.CoreFoundation.CFUrlTest [PASS] FromFile_Null [PASS] FromUrlString_Null [PASS] RetainCountFromFile [PASS] RetainCountFromUrl [PASS] ToString_ MonoTouchFixtures.CoreFoundation.CFUrlTest : 0.4314 ms MonoTouchFixtures.CoreFoundation.DispatchBlockTest [PASS] Cancellation [PASS] Constructors [PASS] Create [PASS] ExplicitActionConversionInvoke [PASS] Invoke [PASS] NotifyAction [PASS] NotifyDispatchBlock [PASS] Wait_DispatchTime [PASS] Wait_TimeSpan MonoTouchFixtures.CoreFoundation.DispatchBlockTest : 407.9183 ms MonoTouchFixtures.CoreFoundation.DispatchDataTest [PASS] FromByteBufferTest [PASS] FromReadOnlySpanTest MonoTouchFixtures.CoreFoundation.DispatchDataTest : 0.3207 ms MonoTouchFixtures.CoreFoundation.DispatchGroupTest [PASS] EnterLeaveTest [PASS] NotifyWithAction [PASS] NotifyWithDispatchBlock [PASS] WaitTest MonoTouchFixtures.CoreFoundation.DispatchGroupTest : 203.0045 ms MonoTouchFixtures.CoreFoundation.DispatchQueueTests [PASS] CtorWithAttributes [PASS] DispatchAsync [PASS] DispatchBarrierAsync [PASS] DispatchBarrierSync [PASS] DispatchSync [PASS] MainQueue [PASS] Specific MonoTouchFixtures.CoreFoundation.DispatchQueueTests : 1.7009 ms MonoTouchFixtures.CoreFoundation.DispatchTests [PASS] Current [PASS] Default [IGNORED] EverAfter : UIKitThreadAccessException is not throw, by default, on release builds (removed by the linker) at MonoTouchFixtures.CoreFoundation.DispatchTests.EverAfter() [IGNORED] EverAfterQualityOfService : UIKitThreadAccessException is not throw, by default, on release builds (removed by the linker) at MonoTouchFixtures.CoreFoundation.DispatchTests.EverAfterQualityOfService() [PASS] GetGlobalQueue_Priority [PASS] GetGlobalQueue_QualityOfService [PASS] Main [IGNORED] MainQueueDispatch : UIKitThreadAccessException is not throw, by default, on release builds (removed by the linker) at MonoTouchFixtures.CoreFoundation.DispatchTests.MainQueueDispatch() [IGNORED] MainQueueDispatchQualityOfService : UIKitThreadAccessException is not throw, by default, on release builds (removed by the linker) at MonoTouchFixtures.CoreFoundation.DispatchTests.MainQueueDispatchQualityOfService() [PASS] NeverTooLate [PASS] SetTargetQueue MonoTouchFixtures.CoreFoundation.DispatchTests : 1001.3958 ms MonoTouchFixtures.CoreFoundation.MutableString [PASS] AppendString [PASS] AppendString_RtL [PASS] AppendString_Unicode [PASS] CreateCFString1 [PASS] CreateCFString2 [PASS] CreateString0 [PASS] CreateString1 [PASS] CreateString2 [PASS] TransformICU [PASS] TransformNoRangeEnum [PASS] TransformNull [PASS] TransformRangeEnum MonoTouchFixtures.CoreFoundation.MutableString : 8.3952 ms MonoTouchFixtures.CoreFoundation.NativeObjectTest [PASS] Create [PASS] CreateInvalidHandle [PASS] Dispose [PASS] Equals [PASS] GetHashCodeValue [PASS] ReleaseAfterDispose [PASS] RetainAfterDispose MonoTouchFixtures.CoreFoundation.NativeObjectTest : 0.8092 ms MonoTouchFixtures.CoreFoundation.NetworkTest [IGNORED] Bug_7923 : Only run when proxy is configured. at MonoTouchFixtures.CoreFoundation.NetworkTest.Bug_7923() [PASS] GetProxiesForUri [PASS] WebProxy MonoTouchFixtures.CoreFoundation.NetworkTest : 211.9642 ms MonoTouchFixtures.CoreFoundation.NotificationCenterTest [PASS] Static MonoTouchFixtures.CoreFoundation.NotificationCenterTest : 0.085 ms MonoTouchFixtures.CoreFoundation.OSLogTest [PASS] Custom [PASS] Default MonoTouchFixtures.CoreFoundation.OSLogTest : 0.2236 ms MonoTouchFixtures.CoreFoundation.ProxyTest [PASS] Fields [PASS] TestPACParsingAsync [PASS] TestPACParsingAsyncNoProxy Not ignoring test, because not running in CI: CI bots might have proxies setup and will mean that the test will fail when trying to assert they are empty. [PASS] TestPACParsingScript [PASS] TestPACParsingScriptError [PASS] TestPACParsingScriptNoProxy [PASS] TestPACParsingUrl [PASS] TestPACParsingUrlAsync [PASS] TestPACParsingUrlAsyncNoProxy Not ignoring test, because not running in CI: CI bots might have proxies setup and will mean that the test will fail when trying to assert they are empty. [PASS] TestPacParsingUrlNoProxy MonoTouchFixtures.CoreFoundation.ProxyTest : 2768.2931 ms MonoTouchFixtures.CoreFoundation.RunLoopTest [PASS] AllModes [PASS] CurrentMode [PASS] RunInMode MonoTouchFixtures.CoreFoundation.RunLoopTest : 0.648 ms MonoTouchFixtures.CoreFoundation.StringTest [PASS] Index [PASS] Null [PASS] ToString_ MonoTouchFixtures.CoreFoundation.StringTest : 0.217 ms MonoTouchFixtures.CoreFoundation : 5729.4248 ms MonoTouchFixtures.CoreGraphics MonoTouchFixtures.CoreGraphics.AffineTransformTest [PASS] Ctor [PASS] Decompose [PASS] Invert [PASS] IsIdentity [PASS] MakeIdentity [PASS] MakeRotation [PASS] MakeScale [PASS] MakeTranslation [PASS] MakeWithComponents [PASS] Multiply [PASS] NSValueRoundtrip [PASS] Rotate [PASS] Scale [PASS] SizeOfTest [PASS] StaticMultiply [PASS] StaticRotate [PASS] StaticScale [PASS] StaticTranslate [PASS] ToStringTest [PASS] TransformPoint [PASS] TransformRect [PASS] Translate MonoTouchFixtures.CoreGraphics.AffineTransformTest : 8.8351 ms MonoTouchFixtures.CoreGraphics.BitmapContextTest [IGNORED] CreateAdaptive : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreGraphics.BitmapContextTest.CreateAdaptive() [PASS] Ctor_CGBitmapFlags [PASS] Ctor_CGColorSpace_Null [PASS] Ctor_CGImageAlphaInfo [PASS] ToImage MonoTouchFixtures.CoreGraphics.BitmapContextTest : 1.4085 ms MonoTouchFixtures.CoreGraphics.CGBitmapInfoTest [PASS] Extensions MonoTouchFixtures.CoreGraphics.CGBitmapInfoTest : 0.0993 ms MonoTouchFixtures.CoreGraphics.CGBitmapParametersTest [PASS] ByteOrderTest [PASS] DefaultValuesTest [PASS] HasPremultipliedAlphaFalseTest [PASS] PropertySetGetTest MonoTouchFixtures.CoreGraphics.CGBitmapParametersTest : 0.5414 ms MonoTouchFixtures.CoreGraphics.CGContentInfoTest [PASS] DefaultValuesTest [PASS] HasTransparencyFalseTest [PASS] HasWideGamutFalseTest [PASS] PropertySetGetTest MonoTouchFixtures.CoreGraphics.CGContentInfoTest : 0.1697 ms MonoTouchFixtures.CoreGraphics.CGContentToneMappingInfoTest [PASS] DefaultValuesTest [PASS] PropertySetGetTest MonoTouchFixtures.CoreGraphics.CGContentToneMappingInfoTest : 0.6555 ms MonoTouchFixtures.CoreGraphics.CGDisplayTest [PASS] GetRefreshRate MonoTouchFixtures.CoreGraphics.CGDisplayTest : 0.2339 ms MonoTouchFixtures.CoreGraphics.CGEventTests [PASS] Constructor_CGEventSourceStateID_0 [PASS] Constructor_CGEventSourceStateID_1 [PASS] Constructor_CGEventSourceStateID_2 [PASS] Constructor_CGEventSourceStateID_3 [PASS] Constructor_CGEventSourceStateID_4 [PASS] PostToPSN MonoTouchFixtures.CoreGraphics.CGEventTests : 22.1652 ms MonoTouchFixtures.CoreGraphics.CGImageMetadataTest [PASS] EnumerateMetadata MonoTouchFixtures.CoreGraphics.CGImageMetadataTest : 1.1863 ms MonoTouchFixtures.CoreGraphics.CGImageTest [PASS] ContentHeadroom [PASS] FromPNG MonoTouchFixtures.CoreGraphics.CGImageTest : 5.135 ms MonoTouchFixtures.CoreGraphics.CGPointDictionaryTests [PASS] Default [PASS] PropertiesTest [PASS] ToStringTest1 [PASS] ToStringTest2 MonoTouchFixtures.CoreGraphics.CGPointDictionaryTests : 0.9303 ms MonoTouchFixtures.CoreGraphics.CGRectDictionaryTests [PASS] Default [PASS] PropertiesTest [PASS] ToStringTest1 [PASS] ToStringTest2 MonoTouchFixtures.CoreGraphics.CGRectDictionaryTests : 0.9995 ms MonoTouchFixtures.CoreGraphics.CGRenderingBufferProviderTest [IGNORED] CreateWithCFData_ReturnsNull : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreGraphics.CGRenderingBufferProviderTest.CreateWithCFData_ReturnsNull() [IGNORED] GetTypeId : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreGraphics.CGRenderingBufferProviderTest.GetTypeId() [IGNORED] SizeProperty_DoesNotThrow : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreGraphics.CGRenderingBufferProviderTest.SizeProperty_DoesNotThrow() MonoTouchFixtures.CoreGraphics.CGRenderingBufferProviderTest : 0.3579 ms MonoTouchFixtures.CoreGraphics.CGShadingTest [IGNORED] CreateAxialWithContentHeadroom : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreGraphics.CGShadingTest.CreateShadingWithContentHeadroomTest(Func`4 createShading) at MonoTouchFixtures.CoreGraphics.CGShadingTest.CreateAxialWithContentHeadroom() [IGNORED] CreateRadialWithContentHeadroom : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreGraphics.CGShadingTest.CreateShadingWithContentHeadroomTest(Func`4 createShading) at MonoTouchFixtures.CoreGraphics.CGShadingTest.CreateRadialWithContentHeadroom() MonoTouchFixtures.CoreGraphics.CGShadingTest : 0.2628 ms MonoTouchFixtures.CoreGraphics.CGSizeDictionaryTests [PASS] Default [PASS] PropertiesTest [PASS] ToStringTest1 [PASS] ToStringTest2 MonoTouchFixtures.CoreGraphics.CGSizeDictionaryTests : 0.588 ms MonoTouchFixtures.CoreGraphics.CGToneMappingOptionsTest [IGNORED] DefaultOptions : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreGraphics.CGToneMappingOptionsTest.DefaultOptions() MonoTouchFixtures.CoreGraphics.CGToneMappingOptionsTest : 0.1031 ms MonoTouchFixtures.CoreGraphics.ColorConversionInfoTest [PASS] CGColorSpace_CGColorSpace_CGColorConversionOptions [PASS] CGColorSpace_CGColorSpace_NSDictionary [PASS] CreateDual [PASS] CreateMax [PASS] CreateNone [PASS] CreateSimple [PASS] CreateSimple_DeviceColorSpace [PASS] CreateSimple_GetINativeObject [PASS] CreateSingle [PASS] CreateTooMany MonoTouchFixtures.CoreGraphics.ColorConversionInfoTest : 1.8198 ms MonoTouchFixtures.CoreGraphics.ColorSpaceTest [PASS] CalibratedGray [PASS] CalibratedRGB [PASS] CGColorSpaceUsesITUR_2100TFTest [PASS] CopyBaseColorSpace [PASS] CreateCopyWithStandardRange [PASS] CreateDeviceCMYK [PASS] CreateDeviceGray [PASS] CreateDeviceRGB [PASS] CreateExtendedLinearizedTest [PASS] CreateExtendedSrgb [PASS] CreateExtendedTest [PASS] CreateIccData [PASS] CreateIccData_Null_CGDataProvider [PASS] CreateIccData_Null_NSData [PASS] CreateICCProfile [PASS] CreateIndexed [PASS] CreateLinearizedTest [PASS] Disposed [PASS] Indexed_Provider [PASS] Indexed_UIImage [PASS] IsHdr [PASS] IsHlgBasedTest [PASS] IsPQBasedTest [PASS] Lab MonoTouchFixtures.CoreGraphics.ColorSpaceTest : 4.7185 ms MonoTouchFixtures.CoreGraphics.ColorTest [PASS] Cmyk [PASS] ColorSpace [IGNORED] ContentHeadroom : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreGraphics.ColorTest.ContentHeadroom() [PASS] CreateByMatchingToColorSpace [PASS] CreateGenericGray [PASS] CreateGenericGrayGamma2_2 [PASS] CreateGenericRGB [PASS] CreateSrgb [PASS] GetAXName [PASS] GetConstantColor MonoTouchFixtures.CoreGraphics.ColorTest : 2.6987 ms MonoTouchFixtures.CoreGraphics.ContextTest [PASS] DrawImageApplyingToneMapping [PASS] EdrHeadroom [PASS] ResetClip [PASS] SetLineDash MonoTouchFixtures.CoreGraphics.ContextTest : 0.7615 ms MonoTouchFixtures.CoreGraphics.DataConsumerTest [PASS] Create_Nullable MonoTouchFixtures.CoreGraphics.DataConsumerTest : 0.0942 ms MonoTouchFixtures.CoreGraphics.DataProviderTest [PASS] Create_Nullable [PASS] Create_ReleaseCallback [PASS] CreateWithUnownedMemory [PASS] FromPNG MonoTouchFixtures.CoreGraphics.DataProviderTest : 0.5691 ms MonoTouchFixtures.CoreGraphics.FontTest [PASS] CreateFromProvider [PASS] CreateWithFontName [PASS] GetGlyphWithGlyphName [PASS] Nullable MonoTouchFixtures.CoreGraphics.FontTest : 1.3129 ms MonoTouchFixtures.CoreGraphics.FunctionTest [PASS] CoreGraphicsStrongDictionary [PASS] Test MonoTouchFixtures.CoreGraphics.FunctionTest : 106.1502 ms MonoTouchFixtures.CoreGraphics.GeometryTest [PASS] Infinite [PASS] Null [PASS] Zero MonoTouchFixtures.CoreGraphics.GeometryTest : 0.3228 ms MonoTouchFixtures.CoreGraphics.GradientTest [PASS] Colorspace_Null [PASS] Colorspaces [IGNORED] CreateWithHeadroom : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreGraphics.GradientTest.CreateWithHeadroom() [PASS] GradientDrawingOptions [PASS] Nullable MonoTouchFixtures.CoreGraphics.GradientTest : 1.1886 ms MonoTouchFixtures.CoreGraphics.PathTest [PASS] AddArc [PASS] AddArcToPoint [PASS] AddCurveToPoint [PASS] AddEllipseInRect [PASS] AddLines [PASS] AddLineToPoint [PASS] AddPath [PASS] AddQuadCurveToPoint [PASS] AddRect [PASS] AddRects [PASS] AddRelativeArc [PASS] Bug40230 [PASS] CopyByDashingPath_18764 [PASS] CreateByFlattening [PASS] DoesIntersect [PASS] EllipseFromRect [PASS] GetSeparateComponents [PASS] IncreaseRetainCountMakeMutable [PASS] Intersecting [PASS] LineByIntersecting [PASS] LineBySubtracting [PASS] MoveToPoint [PASS] Normalizing [PASS] Subtracting [PASS] SymmetricDifference [PASS] Union MonoTouchFixtures.CoreGraphics.PathTest : 1.7547 ms MonoTouchFixtures.CoreGraphics.PatternTest [PASS] DrawTest MonoTouchFixtures.CoreGraphics.PatternTest : 101.5149 ms MonoTouchFixtures.CoreGraphics.PDFContentStreamTest [PASS] FromPage MonoTouchFixtures.CoreGraphics.PDFContentStreamTest : 1.8786 ms MonoTouchFixtures.CoreGraphics.PDFContextTest [IGNORED] Constructors : Crash (at least on simulator) with iOS 11.3 beta 1 at MonoTouchFixtures.CoreGraphics.PDFContextTest.Constructors() [PASS] Context_Tag [PASS] Context_Tag_Strong [PASS] Context_Url [PASS] Context_Url_Rect [PASS] Context_Url_Rect_Info [PASS] SetIdTree [PASS] SetPageTagStructureTree [PASS] SetParentTree MonoTouchFixtures.CoreGraphics.PDFContextTest : 8.8435 ms MonoTouchFixtures.CoreGraphics.PDFDocumentTest [PASS] DataProvider [PASS] FromFile [PASS] FromUrl MonoTouchFixtures.CoreGraphics.PDFDocumentTest : 13.6915 ms MonoTouchFixtures.CoreGraphics.PDFInfoTest [IGNORED] ToDictionary : Crash (at least on devices) with iOS 11.3 beta 1 at MonoTouchFixtures.CoreGraphics.PDFInfoTest.ToDictionary() [PASS] ToDictionaryWithPermissions MonoTouchFixtures.CoreGraphics.PDFInfoTest : 5.9457 ms MonoTouchFixtures.CoreGraphics.PDFOperatorTableTest [PASS] Default MonoTouchFixtures.CoreGraphics.PDFOperatorTableTest : 0.1422 ms MonoTouchFixtures.CoreGraphics.PDFScannerTest [PASS] Tamarin MonoTouchFixtures.CoreGraphics.PDFScannerTest : 0.6836 ms MonoTouchFixtures.CoreGraphics.PdfTagTypeTest [PASS] EnumExtension MonoTouchFixtures.CoreGraphics.PdfTagTypeTest : 0.1817 ms MonoTouchFixtures.CoreGraphics.PointTest [PASS] ToStringTest MonoTouchFixtures.CoreGraphics.PointTest : 0.4606 ms MonoTouchFixtures.CoreGraphics.RectTest [PASS] Empty [PASS] Infinite [PASS] Inflate [PASS] Null [PASS] ToStringTest MonoTouchFixtures.CoreGraphics.RectTest : 0.5981 ms MonoTouchFixtures.CoreGraphics.SizeTest [PASS] ToStringTest MonoTouchFixtures.CoreGraphics.SizeTest : 0.3195 ms MonoTouchFixtures.CoreGraphics.VectorTest [PASS] ToStringTest MonoTouchFixtures.CoreGraphics.VectorTest : 0.3134 ms MonoTouchFixtures.CoreGraphics : 299.8447 ms MonoTouchFixtures.CoreImage MonoTouchFixtures.CoreImage.CIContextTest [PASS] CreateRefCount MonoTouchFixtures.CoreImage.CIContextTest : 14.031 ms MonoTouchFixtures.CoreImage.CIKernelTests [PASS] CIKernel_BasicTest [PASS] CIKernel_TestFromPrograms MonoTouchFixtures.CoreImage.CIKernelTests : 14.8391 ms MonoTouchFixtures.CoreImage.CIVectorTest [PASS] CtorFloatArray [PASS] CtorFloatArrayCount [PASS] CtorInts [PASS] FromValues MonoTouchFixtures.CoreImage.CIVectorTest : 0.806 ms MonoTouchFixtures.CoreImage.DetectorTest [PASS] EmptyOptions [PASS] NullContext MonoTouchFixtures.CoreImage.DetectorTest : 11.4287 ms MonoTouchFixtures.CoreImage.FilterTest [PASS] CIAttributedTextImageGenerator [PASS] CIBarcodeDescriptorTest [PASS] CIVectorArray [PASS] ColorSpace [PASS] CustomFilterTest [PASS] HighlightShadowAdjust MonoTouchFixtures.CoreImage.FilterTest : 13.502 ms MonoTouchFixtures.CoreImage.ImageInitializationOptionsTest [PASS] ColorSpace [PASS] Defaults [PASS] WithMetadataDefaults [PASS] WithMetadataProperties MonoTouchFixtures.CoreImage.ImageInitializationOptionsTest : 0.5775 ms MonoTouchFixtures.CoreImage.ImageTest [PASS] AreaHistogram [PASS] CIImageColorSpaceTest [PASS] EmptyImage [PASS] InitializationWithCustomMetadata [PASS] UIImageInterop MonoTouchFixtures.CoreImage.ImageTest : 4.6934 ms MonoTouchFixtures.CoreImage : 59.9078 ms MonoTouchFixtures.CoreLocation MonoTouchFixtures.CoreLocation.BeaconRegionTest [PASS] Ctor2 [PASS] Ctor3 [PASS] Ctor4 MonoTouchFixtures.CoreLocation.BeaconRegionTest : 0.4485 ms MonoTouchFixtures.CoreLocation.LocationManagerTest [PASS] DeferredLocationUpdatesAvailable MonoTouchFixtures.CoreLocation.LocationManagerTest : 0.0914 ms MonoTouchFixtures.CoreLocation : 0.5463 ms MonoTouchFixtures.CoreMedia MonoTouchFixtures.CoreMedia.BlockBufferTest [PASS] AccessDataBytesTest [PASS] AppendMemoryBlockTest [PASS] AppendMemoryBlockWithByteArrayTest [PASS] AppendMemoryBlockWithManagedMemory [PASS] CMBlockBufferCustomBlockSource [PASS] CopyDataBytesTest [PASS] CopyDataBytesUsingManagedArrayTest [PASS] CreateEmpty [PASS] FromMemoryBlockAndContiguousTest [PASS] FromMemoryBlockWithByteArrayTest [PASS] FromMemoryBlockWithManagedMemory [PASS] GetDataPointerTest [PASS] ReplaceDataBytesManagedTest [PASS] ReplaceDataBytesTest MonoTouchFixtures.CoreMedia.BlockBufferTest : 1.1818 ms MonoTouchFixtures.CoreMedia.CMClockOrTimebaseTest [PASS] RetainReleaseTest MonoTouchFixtures.CoreMedia.CMClockOrTimebaseTest : 0.0807 ms MonoTouchFixtures.CoreMedia.CMClockTest [PASS] CreateAudioClock [PASS] HostTimeClock MonoTouchFixtures.CoreMedia.CMClockTest : 0.4038 ms MonoTouchFixtures.CoreMedia.CMFormatDescriptionTest [PASS] ClosedCaption [PASS] H264ParameterSetsTest [PASS] HevcParameterSetsTest [INCONCLUSIVE] RefcountTest : This test requires video recording permissions. at MonoTouchFixtures.CoreMedia.CMFormatDescriptionTest.RefcountTest() [IGNORED] TagCollections : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreMedia.CMFormatDescriptionTest.TagCollections() [PASS] Video [PASS] VideoFormatDescriptionConstructors MonoTouchFixtures.CoreMedia.CMFormatDescriptionTest : 95.3599 ms MonoTouchFixtures.CoreMedia.CMMemoryPoolTest [PASS] Ctor [PASS] CtorAgeOutPeriod MonoTouchFixtures.CoreMedia.CMMemoryPoolTest : 0.4802 ms MonoTouchFixtures.CoreMedia.CMTagCollectionTests [PASS] AddCollection [PASS] AddMutableTest [PASS] AddTags [PASS] AddTest [PASS] ApplyTest [PASS] ApplyUntilTest [PASS] ContainsCategoryTest [PASS] ContainsTagCollectionTest [PASS] ContainsTagsTest [PASS] ContainsTagTest [PASS] CopyTest [PASS] CreateMutableCopyTest [PASS] CreateMutableTest [PASS] CreateTest [PASS] CreateTest_OSStatus [PASS] CreateWithCopyOfTags_Filter [PASS] Data [PASS] Dictionary [PASS] ExclusiveOr [PASS] ExclusiveOr_Instance [PASS] GetCount_Filter [PASS] GetCountTest [PASS] GetTags_Filter [PASS] GetTags2_Filter [PASS] GetTags2Test [PASS] GetTagsForCategory2Test [PASS] GetTagsForCategoryTest [PASS] GetTagsTest [PASS] GetTypeIdTest [PASS] Intersect [PASS] Intersect_Instance [PASS] RemoveAllMutableTest [PASS] RemoveAllTest [PASS] RemoveMutableTest [PASS] RemoveTest [PASS] Subtract [PASS] Subtract_Instance [PASS] TagsTest [PASS] ToStringTest [PASS] Union [PASS] Union_Instance MonoTouchFixtures.CoreMedia.CMTagCollectionTests : 2.8573 ms MonoTouchFixtures.CoreMedia.CMTaggedBufferGroupTests [PASS] Combine [PASS] Create_MediaBuffers [PASS] Create_MixedBuffers [PASS] Create_PixelBuffers [PASS] CreateFormatDescription [IGNORED] CreateFormatDescriptionWithExtensions : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreMedia.CMTaggedBufferGroupTests.CreateFormatDescriptionWithExtensions() [PASS] CreateSampleBuffer [PASS] GetMixedBuffers [PASS] GetNumberOfMatches [PASS] GetPixelBuffer [PASS] GetPixelBuffer_Tag [PASS] GetPixelBuffer_TagCollection [PASS] GetSampleBuffer [PASS] GetSampleBuffer_Tag [PASS] GetSampleBuffer_TagCollection [PASS] GetTagCollection [PASS] GetTaggedBufferGroup [PASS] GetTypeIdTest [PASS] Matches MonoTouchFixtures.CoreMedia.CMTaggedBufferGroupTests : 2.1925 ms MonoTouchFixtures.CoreMedia.CMTagTests [PASS] Compare [PASS] Create [PASS] Default [PASS] Dictionary [PASS] Equals [PASS] Fields [PASS] Hash [PASS] ToStringTests MonoTouchFixtures.CoreMedia.CMTagTests : 1.9908 ms MonoTouchFixtures.CoreMedia.CMTimebaseTest [PASS] AddTimer [PASS] CMClockConstructor [PASS] CMTimebaseConstructor [PASS] CopyMasterTests [PASS] DefaultValues [PASS] GetMasterTests [PASS] SetAnchorTime [PASS] SourceClockProperty [PASS] SourceTimebaseProperty MonoTouchFixtures.CoreMedia.CMTimebaseTest : 1.0252 ms MonoTouchFixtures.CoreMedia.CMTimeRangeTests [PASS] InvalidMappingFieldTest [PASS] InvalidRangeFieldTest [PASS] ZeroFieldTest MonoTouchFixtures.CoreMedia.CMTimeRangeTests : 0.2108 ms MonoTouchFixtures.CoreMedia.CMTimeTests [PASS] CMTimeMappingFactoryMethods [PASS] CMTimeRangeConstants [PASS] CMTimeStrongDictionary [PASS] MethodsTest [PASS] MultiplyByRatio [PASS] PropertiesTest MonoTouchFixtures.CoreMedia.CMTimeTests : 1.9011 ms MonoTouchFixtures.CoreMedia.EnumTest [PASS] ClosedCaptionFormatType [PASS] MediaType [PASS] MetadataFormatType [PASS] MuxedStreamType [PASS] PixelFormat [PASS] SubtitleFormatType [PASS] TimeCodeFormatType [PASS] VideoCodecType MonoTouchFixtures.CoreMedia.EnumTest : 0.2911 ms MonoTouchFixtures.CoreMedia.SampleBufferTest [PASS] CallForEachSample [PASS] CreateForImageBuffer [PASS] CreateReady [PASS] CreateReadyWithImageBuffer [PASS] CreateReadyWithPacketDescriptions [PASS] SetInvalidateCallback [PASS] SetInvalidateCallback_Null [PASS] SetInvalidateCallback_Replace MonoTouchFixtures.CoreMedia.SampleBufferTest : 0.6645 ms MonoTouchFixtures.CoreMedia : 108.7203 ms MonoTouchFixtures.CoreMidi MonoTouchFixtures.CoreMidi.Midi2DeviceManufacturerTest [PASS] SysExIdByte MonoTouchFixtures.CoreMidi.Midi2DeviceManufacturerTest : 0.1335 ms MonoTouchFixtures.CoreMidi.Midi2DeviceRevisionLevelTest [PASS] RevisionLevel MonoTouchFixtures.CoreMidi.Midi2DeviceRevisionLevelTest : 0.0767 ms MonoTouchFixtures.CoreMidi.MidiCIProfileIdTest [PASS] ManufacturerSpecific [PASS] Standard MonoTouchFixtures.CoreMidi.MidiCIProfileIdTest : 0.0658 ms MonoTouchFixtures.CoreMidi.MidiClientTest [PASS] SendTest MonoTouchFixtures.CoreMidi.MidiClientTest : 0.5131 ms MonoTouchFixtures.CoreMidi.MidiEndpointTest [PASS] CorrectDisposeTest MonoTouchFixtures.CoreMidi.MidiEndpointTest : 0.0929 ms MonoTouchFixtures.CoreMidi.MidiThruConnectionParamsTests [PASS] MidiValueMapTest [PASS] ParamsTest [PASS] PropertiesTest_ChannelMap [PASS] PropertiesTest_ChannelPressure [PASS] PropertiesTest_Controls [PASS] PropertiesTest_Destinations [PASS] PropertiesTest_FilterOutAllControls [PASS] PropertiesTest_FilterOutBeatClock [PASS] PropertiesTest_FilterOutMtc [PASS] PropertiesTest_FilterOutSysEx [PASS] PropertiesTest_FilterOutTuneRequest [PASS] PropertiesTest_HighNote [PASS] PropertiesTest_HighVelocity [PASS] PropertiesTest_KeyPressure [PASS] PropertiesTest_LowNote [PASS] PropertiesTest_LowVelocity [PASS] PropertiesTest_Maps [PASS] PropertiesTest_NoteNumber [PASS] PropertiesTest_PitchBend [PASS] PropertiesTest_ProgramChange [PASS] PropertiesTest_Sources [PASS] PropertiesTest_Velocity [PASS] ReadStructTest_ChannelMap [PASS] ReadStructTest_ChannelPressure [PASS] ReadStructTest_Controls [PASS] ReadStructTest_Default [PASS] ReadStructTest_Destinations [PASS] ReadStructTest_FilterOutAllControls [PASS] ReadStructTest_FilterOutBeatClock [PASS] ReadStructTest_FilterOutMtc [PASS] ReadStructTest_FilterOutSysEx [PASS] ReadStructTest_FilterOutTuneRequest [PASS] ReadStructTest_HighNode [PASS] ReadStructTest_HighVelocity [PASS] ReadStructTest_KeyPressure [PASS] ReadStructTest_LowNote [PASS] ReadStructTest_LowVelocity [PASS] ReadStructTest_Maps [PASS] ReadStructTest_NoteNumber [PASS] ReadStructTest_PitchBend [PASS] ReadStructTest_ProgramChange [PASS] ReadStructTest_Sources [PASS] ReadStructTest_Velocity MonoTouchFixtures.CoreMidi.MidiThruConnectionParamsTests : 128.9053 ms MonoTouchFixtures.CoreMidi.MidiThruConnectionTests [IGNORED] ConnectionCreateTest : OneTimeSetUp: https://github.com/xamarin/maccore/issues/1834 [IGNORED] FindTest : OneTimeSetUp: https://github.com/xamarin/maccore/issues/1834 [IGNORED] GetSetParamsTest : OneTimeSetUp: https://github.com/xamarin/maccore/issues/1834 [SKIPREASON] https://github.com/xamarin/maccore/issues/1834 MonoTouchFixtures.CoreMidi.MidiThruConnectionTests : 0.0142 ms MonoTouchFixtures.CoreMidi : 129.8399 ms MonoTouchFixtures.CoreML MonoTouchFixtures.CoreML.MLMultiArrayTest [PASS] Ctors [PASS] Indexers MonoTouchFixtures.CoreML.MLMultiArrayTest : 2.0908 ms MonoTouchFixtures.CoreML : 2.105 ms MonoTouchFixtures.CoreMotion MonoTouchFixtures.CoreMotion.MediaTimingFunctionTest [PASS] Default [PASS] EaseIn [PASS] EaseInEaseOut [PASS] EaseOut [PASS] GetControlPoint [PASS] Linear MonoTouchFixtures.CoreMotion.MediaTimingFunctionTest : 0.9592 ms MonoTouchFixtures.CoreMotion : 0.9822 ms MonoTouchFixtures.CoreServices MonoTouchFixtures.CoreServices.HttpMessageTest [PASS] CreateEmptyFalse [PASS] CreateEmptyTrue [PASS] CreateRequest10 [IGNORED] CreateResponseAuth : The site http://httpbin.org is not accessible. This test will be ignored. at MonoTests.System.Net.Http.NetworkResources.AssertNetworkConnection(String url) at MonoTests.System.Net.Http.NetworkResources.Httpbin.get_HttpUrl() at MonoTests.System.Net.Http.NetworkResources.Httpbin.GetStatusCodeUrl(HttpStatusCode status) at MonoTouchFixtures.CoreServices.HttpMessageTest.<>c__DisplayClass3_0.<b__0>d.MoveNext() --- End of stack trace from previous location --- at TestRuntime.TryRunAsync(TimeSpan timeout, Task startTask, Task completionTask, UIImage imageToShow, Exception& exception) at TestRuntime.RunAsync(TimeSpan timeout, Task startTask, Func`1 check_completed, UIImage imageToShow) at MonoTouchFixtures.CoreServices.HttpMessageTest.CreateResponseAuth() MonoTouchFixtures.CoreServices.HttpMessageTest : 222.4899 ms MonoTouchFixtures.CoreServices : 222.4988 ms MonoTouchFixtures.CoreText MonoTouchFixtures.CoreText.CTFontCollectionTest [PASS] GetMatchingFontDescriptorsCollectionOptionsTest [PASS] GetMatchingFontDescriptorsTest MonoTouchFixtures.CoreText.CTFontCollectionTest : 72.0294 ms MonoTouchFixtures.CoreText.CTFrameTests [PASS] CTTypesetterCreateTest [PASS] GetPathTest {{inf, inf}, {0, 0}} MonoTouchFixtures.CoreText.CTFrameTests : 0.6801 ms MonoTouchFixtures.CoreText.CTLineTests [PASS] EnumerateCaretOffsets [PASS] GetImageBounds MonoTouchFixtures.CoreText.CTLineTests : 1.276 ms MonoTouchFixtures.CoreText.CTParagraphStyleTests [PASS] StylePropertiesTest MonoTouchFixtures.CoreText.CTParagraphStyleTests : 1.4848 ms MonoTouchFixtures.CoreText.FontDescriptorTest [PASS] FromAttributes [PASS] MatchFontDescriptors [PASS] WithFeature MonoTouchFixtures.CoreText.FontDescriptorTest : 108.8892 ms MonoTouchFixtures.CoreText.FontManagerTest [PASS] CreateFontDescriptor [PASS] CreateFontDescriptors [PASS] GetFontsMissing [PASS] GetFontsNullUrl [PASS] GetFontsPresent [PASS] RegisterFontDescriptors_NoCallback [PASS] RegisterFontDescriptors_Null [PASS] RegisterFontDescriptors_WithCallback [PASS] RegisterFonts_NoCallback [PASS] RegisterFonts_Null [PASS] RegisterFonts_WithCallback [PASS] RegisterTTF [PASS] RegisterTTFs [PASS] UnregisterFontDescriptors_Null [PASS] UnregisterFonts_Null MonoTouchFixtures.CoreText.FontManagerTest : 29.4871 ms MonoTouchFixtures.CoreText.FontTest [PASS] CTFontCopyNameForGlyph [PASS] CTFontCreateForString [PASS] CTFontCreateForStringWithLanguage [PASS] CTFontCreateWithFontDescriptorAndOptions [PASS] CTFontCreateWithNameAndOptions [PASS] DrawImage [PASS] GetCascadeList [PASS] GetGlyphsForCharacters_35048 [PASS] GetLocalizedName [PASS] GetTypographicBoundsForAdaptiveImageProvider MonoTouchFixtures.CoreText.FontTest : 7.2777 ms MonoTouchFixtures.CoreText.GlyphInfoTest [PASS] GlyphInfo MonoTouchFixtures.CoreText.GlyphInfoTest : 1.1245 ms MonoTouchFixtures.CoreText.RunTest [PASS] CustomOps [PASS] GetBaseAdvancesAndOrigins [PASS] Runs MonoTouchFixtures.CoreText.RunTest : 0.7713 ms MonoTouchFixtures.CoreText.StringAttributesTests [PASS] BackgroundColor [PASS] HorizontalInVerticalForms [PASS] NoCTLine [PASS] SimpleValuesSet MonoTouchFixtures.CoreText.StringAttributesTests : 2.2226 ms MonoTouchFixtures.CoreText : 225.3048 ms MonoTouchFixtures.CoreVideo MonoTouchFixtures.CoreVideo.CoreVideoEnumTest [PASS] PixelFormatType MonoTouchFixtures.CoreVideo.CoreVideoEnumTest : 0.1341 ms MonoTouchFixtures.CoreVideo.CVImageBufferTests [PASS] CVImageBufferColorPrimariesTest [PASS] CVImageBufferTransferFunctionTest [PASS] CVImageBufferYCbCrMatrixTest MonoTouchFixtures.CoreVideo.CVImageBufferTests : 0.7587 ms MonoTouchFixtures.CoreVideo.CVMetalBufferCacheTests MonoTouchFixtures.CoreVideo.CVMetalBufferCacheTests.CreateBufferFromImageTest [PASS] CreateBufferFromImageTest(CV32BGRA) MonoTouchFixtures.CoreVideo.CVMetalBufferCacheTests.CreateBufferFromImageTest : 0.6294 ms [PASS] CtorTest_CVMetalBufferCacheAttributes [PASS] CtorTest_NSDictionary [PASS] FlushTest [PASS] GetTypeIdTest [PASS] TryCreate [PASS] TryCreateHandle MonoTouchFixtures.CoreVideo.CVMetalBufferCacheTests : 1.0198 ms MonoTouchFixtures.CoreVideo.CVMetalBufferTests MonoTouchFixtures.CoreVideo.CVMetalBufferTests.GetMetalBufferTest [PASS] GetMetalBufferTest(CV32BGRA) MonoTouchFixtures.CoreVideo.CVMetalBufferTests.GetMetalBufferTest : 0.2467 ms [PASS] GetTypeIdTest MonoTouchFixtures.CoreVideo.CVMetalBufferTests : 0.3702 ms MonoTouchFixtures.CoreVideo.CVMetalTextureCacheTests [IGNORED] CVMetalTextureCacheCtorTest : This test only runs on device. at MonoTouchFixtures.CoreVideo.CVMetalTextureCacheTests.CVMetalTextureCacheCtorTest() [IGNORED] FromDeviceTest : This test only runs on device. at MonoTouchFixtures.CoreVideo.CVMetalTextureCacheTests.FromDeviceTest() MonoTouchFixtures.CoreVideo.CVMetalTextureCacheTests : 0.2414 ms MonoTouchFixtures.CoreVideo.CVPixelFormatTypeTest [IGNORED] Extensions : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.CoreVideo.CVPixelFormatTypeTest.Extensions() MonoTouchFixtures.CoreVideo.CVPixelFormatTypeTest : 0.1078 ms MonoTouchFixtures.CoreVideo.PixelBufferAttributesTest [PASS] Defaults [PASS] MemoryAllocator MonoTouchFixtures.CoreVideo.PixelBufferAttributesTest : 0.2924 ms MonoTouchFixtures.CoreVideo.PixelBufferPoolTest [PASS] AllocationSettings_Threshold MonoTouchFixtures.CoreVideo.PixelBufferPoolTest : 0.3759 ms MonoTouchFixtures.CoreVideo.PixelBufferTest [PASS] CheckInvalidPtr [PASS] CreateWithBytes [PASS] CreateWithPlanarBytes [PASS] IsCompatibleWithAttributeTest MonoTouchFixtures.CoreVideo.PixelBufferTest : 2.3735 ms MonoTouchFixtures.CoreVideo.PixelFormatDescriptionTest [PASS] AllTypes [PASS] Create [PASS] CV32ARGB [PASS] Register MonoTouchFixtures.CoreVideo.PixelFormatDescriptionTest : 6.1967 ms MonoTouchFixtures.CoreVideo : 11.927 ms MonoTouchFixtures.EventKit MonoTouchFixtures.EventKit.AlarmTest [PASS] NullAllowedTest MonoTouchFixtures.EventKit.AlarmTest : 0.4953 ms MonoTouchFixtures.EventKit.EventKitCalendarTest [PASS] FromEventStore EKEventStore.GetAuthorizationStatus (Event): NotDetermined EKEventStore.GetAuthorizationStatus (Reminder): NotDetermined [PASS] FromEventStore_Null [PASS] FromEventStoreWithReminder EKEventStore.GetAuthorizationStatus (Event): NotDetermined EKEventStore.GetAuthorizationStatus (Reminder): NotDetermined [PASS] Title EKEventStore.GetAuthorizationStatus (Event): NotDetermined EKEventStore.GetAuthorizationStatus (Reminder): NotDetermined MonoTouchFixtures.EventKit.EventKitCalendarTest : 293.4204 ms MonoTouchFixtures.EventKit.EventStoreTest [IGNORED] DefaultCalendar : fail on a cleaned iOS 6 simulator and (differently) on devices MonoTouchFixtures.EventKit.EventStoreTest : 0.0921 ms MonoTouchFixtures.EventKit.RecurrenceRuleTest [PASS] Constructors [PASS] DefaultProperties MonoTouchFixtures.EventKit.RecurrenceRuleTest : 0.8667 ms MonoTouchFixtures.EventKit.ReminderTest [IGNORED] DefaultProperties : OneTimeSetUp: default EKReminder constructor fails in xcode 11 beta 3 - https://github.com/xamarin/maccore/issues/1832 [IGNORED] NullableProperties : OneTimeSetUp: default EKReminder constructor fails in xcode 11 beta 3 - https://github.com/xamarin/maccore/issues/1832 [IGNORED] Range : OneTimeSetUp: default EKReminder constructor fails in xcode 11 beta 3 - https://github.com/xamarin/maccore/issues/1832 [SKIPREASON] default EKReminder constructor fails in xcode 11 beta 3 - https://github.com/xamarin/maccore/issues/1832 MonoTouchFixtures.EventKit.ReminderTest : 0.0068 ms MonoTouchFixtures.EventKit.StructureLocationTest [PASS] DefaultValues [PASS] FromTitle MonoTouchFixtures.EventKit.StructureLocationTest : 0.4347 ms MonoTouchFixtures.EventKit : 295.3633 ms MonoTouchFixtures.EventKitUI MonoTouchFixtures.EventKitUI.EKUIBundleTest [PASS] BundleTest MonoTouchFixtures.EventKitUI.EKUIBundleTest : 0.5216 ms MonoTouchFixtures.EventKitUI : 0.5257 ms MonoTouchFixtures.ExternalAccessory MonoTouchFixtures.ExternalAccessory.AccessoryManagerTest [PASS] Shared MonoTouchFixtures.ExternalAccessory.AccessoryManagerTest : 1.8725 ms MonoTouchFixtures.ExternalAccessory : 1.877 ms MonoTouchFixtures.Foundation MonoTouchFixtures.Foundation.AttributedStringTest [PASS] Attributes [PASS] Create_Data [PASS] Create_Data_Error [PASS] Create_Markdown_Data [PASS] Create_Markdown_Data_Error [PASS] Create_Markdown_String [PASS] Create_Markdown_Url [PASS] Create_Markdown_Url_Error [PASS] Create_Url [PASS] Create_Url_Error [PASS] Fields [PASS] IndirectNullDictionary [PASS] InitWith [PASS] LowLevelGetAttributesOverrideTest [PASS] MutableCopy [PASS] NullDictionary [PASS] UIKitAttachmentConveniences_New MonoTouchFixtures.Foundation.AttributedStringTest : 6.974 ms MonoTouchFixtures.Foundation.BlockOperationTest [PASS] Add_Null [PASS] Create_Null [PASS] ExecutionBlocks MonoTouchFixtures.Foundation.BlockOperationTest : 0.3934 ms MonoTouchFixtures.Foundation.BundleTest [IGNORED] AppStoreReceiptURL : This test only runs on device. at MonoTouchFixtures.Foundation.BundleTest.AppStoreReceiptURL() [PASS] GetLocalizedString [PASS] Localizations [PASS] LocalizedString2 [PASS] LocalizedString3 [PASS] PreferredLocalizations MonoTouchFixtures.Foundation.BundleTest : 0.8267 ms MonoTouchFixtures.Foundation.CachedUrlResponseTest [PASS] ConstructorTest MonoTouchFixtures.Foundation.CachedUrlResponseTest : 0.2709 ms MonoTouchFixtures.Foundation.CalendarTest [PASS] DateByAddingComponentsTest [PASS] DateComponentsTest [PASS] DateFromComponents [PASS] GetAllCalendarIdentifiers [PASS] TestAddingByComponents [PASS] TestCalendarComparision [PASS] TestCalendarComponents [PASS] TestCalendarSymbols [PASS] TestComponentsFromDateToDate [PASS] TestComponentsInTimeZone [PASS] TestEnumerateDates [PASS] TestFindNextDate [PASS] TestFindNextDateAfterDateMatching [PASS] TestIsDateMethods [PASS] TestMatchesComponents MonoTouchFixtures.Foundation.CalendarTest.TestMaximumRange [PASS] TestMaximumRange(1,12,Month) [PASS] TestMaximumRange(1,31,Day) [PASS] TestMaximumRange(0,24,Hour) MonoTouchFixtures.Foundation.CalendarTest.TestMaximumRange : 0.3059 ms MonoTouchFixtures.Foundation.CalendarTest.TestMinimumRange [PASS] TestMinimumRange(1,12,Month) [PASS] TestMinimumRange(1,28,Day) [PASS] TestMinimumRange(0,24,Hour) MonoTouchFixtures.Foundation.CalendarTest.TestMinimumRange : 0.2716 ms [PASS] TestNSCalendarConstructors [PASS] TestNSDateComponentNewAPIs MonoTouchFixtures.Foundation.CalendarTest.TestOrdinality [PASS] TestOrdinality(2010,1,11,Day,Month,11) [PASS] TestOrdinality(2010,4,15,Day,Month,15) MonoTouchFixtures.Foundation.CalendarTest.TestOrdinality : 0.2892 ms MonoTouchFixtures.Foundation.CalendarTest.TestRange [PASS] TestRange(2010,1,11,1,31,Day,Month) [PASS] TestRange(2010,2,11,1,28,Day,Month) [PASS] TestRange(2010,3,11,1,31,Day,Month) [PASS] TestRange(1999,1,11,0,24,Hour,Day) MonoTouchFixtures.Foundation.CalendarTest.TestRange : 0.5217 ms [PASS] TestRangeOfWeekendContainingDate MonoTouchFixtures.Foundation.CalendarTest.TestRangeOrUnitInterval [PASS] TestRangeOrUnitInterval(2010,1,11,Day,86400.0d) [PASS] TestRangeOrUnitInterval(2010,2,11,Hour,3600.0d) [PASS] TestRangeOrUnitInterval(2015,2,11,Month,2419200.0d) MonoTouchFixtures.Foundation.CalendarTest.TestRangeOrUnitInterval : 0.2989 ms MonoTouchFixtures.Foundation.CalendarTest.TestRangeOrUnitIntervalNotNull [PASS] TestRangeOrUnitIntervalNotNull(2010,1,11,Day,86400.0d) [PASS] TestRangeOrUnitIntervalNotNull(2010,2,11,Hour,3600.0d) [PASS] TestRangeOrUnitIntervalNotNull(2015,2,11,Month,2419200.0d) MonoTouchFixtures.Foundation.CalendarTest.TestRangeOrUnitIntervalNotNull : 0.3408 ms [PASS] TestSettingHourComponent [PASS] TestSignUpForDateNotification [PASS] TestStartOfDay MonoTouchFixtures.Foundation.CalendarTest : 11.5095 ms MonoTouchFixtures.Foundation.CoderTest [PASS] EncodeDecodeTest MonoTouchFixtures.Foundation.CoderTest : 0.474 ms MonoTouchFixtures.Foundation.DateFormatterTest [PASS] GetDateFormatFromTemplateTest [PASS] ToLocalizedStringTest MonoTouchFixtures.Foundation.DateFormatterTest : 0.8453 ms MonoTouchFixtures.Foundation.DateTest MonoTouchFixtures.Foundation.DateTest.DateTimeToNSDate [PASS] DateTimeToNSDate(1,1,1,1,1,1,1) [PASS] DateTimeToNSDate(199,1,1,1,1,1,1) [PASS] DateTimeToNSDate(2019,1,1,1,1,1,1) MonoTouchFixtures.Foundation.DateTest.DateTimeToNSDate : 0.4022 ms [PASS] DescriptionWithLocale [PASS] InLimits [PASS] LocalTime MonoTouchFixtures.Foundation.DateTest.RoundTripFromDateTime [PASS] RoundTripFromDateTime(01/01/0001 00:00:00) [PASS] RoundTripFromDateTime(12/31/9999 22:59:59) [PASS] RoundTripFromDateTime(12/31/9999 23:59:59) [PASS] RoundTripFromDateTime(01/01/0001 00:14:00) [PASS] RoundTripFromDateTime(01/01/0001 00:00:00) [PASS] RoundTripFromDateTime(11/20/2025 18:40:54) MonoTouchFixtures.Foundation.DateTest.RoundTripFromDateTime : 0.6872 ms MonoTouchFixtures.Foundation.DateTest.RoundTripFromNSDate [PASS] RoundTripFromNSDate(0001-01-01 00:00:00 +0000,"DistantPast") [PASS] RoundTripFromNSDate(4001-01-01 00:00:00 +0000,"DistantFuture") [PASS] RoundTripFromNSDate(10000-01-01 00:00:00 +0000,"252423993600.00001") MonoTouchFixtures.Foundation.DateTest.RoundTripFromNSDate : 0.2529 ms MonoTouchFixtures.Foundation.DateTest.ThrowsArgumentException [PASS] ThrowsArgumentException(01/01/0001 00:00:00,"DateTime.MinValue") [PASS] ThrowsArgumentException(12/31/9999 23:59:59,"DateTime.MaxValue") [PASS] ThrowsArgumentException(01/01/0001 00:00:00,"default (DateTime)") MonoTouchFixtures.Foundation.DateTest.ThrowsArgumentException : 0.1462 ms MonoTouchFixtures.Foundation.DateTest.ThrowsArgumentOutOfRangeException [PASS] ThrowsArgumentOutOfRangeException(,"double.MaxValue") [PASS] ThrowsArgumentOutOfRangeException(,"double.MinValue") [PASS] ThrowsArgumentOutOfRangeException(0000-12-31 23:59:59 +0000,"-63114076801") [PASS] ThrowsArgumentOutOfRangeException(10000-01-01 00:00:01 +0000,"252423993601") [PASS] ThrowsArgumentOutOfRangeException(10000-01-01 00:00:00 +0000,"252423993600.0001") MonoTouchFixtures.Foundation.DateTest.ThrowsArgumentOutOfRangeException : 0.3365 ms MonoTouchFixtures.Foundation.DateTest : 2.7792 ms MonoTouchFixtures.Foundation.DecimalNumberTest [PASS] One MonoTouchFixtures.Foundation.DecimalNumberTest : 0.3129 ms MonoTouchFixtures.Foundation.DecimalTest [PASS] CastDecimal [PASS] CastDouble [PASS] CastFloat [PASS] CastInt MonoTouchFixtures.Foundation.DecimalTest : 2.0761 ms MonoTouchFixtures.Foundation.DictionaryContainerTest [PASS] Empty [IGNORED] FloatArray : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Foundation.DictionaryContainerTest.FloatArray() [IGNORED] Matrix : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Foundation.DictionaryContainerTest.Matrix() [PASS] SetArrayValue_INativeObject [PASS] SetArrayValue_NSNumber [PASS] SetArrayValue_String [PASS] SetArrayValue_T_Enum [PASS] SetBooleanValue [PASS] SetNumberValue_Int32 [PASS] SetNumberValue_UInt32 [PASS] SetStringValue [PASS] WrappedNSDictionary MonoTouchFixtures.Foundation.DictionaryContainerTest : 9.7711 ms MonoTouchFixtures.Foundation.DimensionTest [PASS] BaseUnit [PASS] NSUnitAcceleration_BaseUnit [PASS] NSUnitAngle_BaseUnit [PASS] NSUnitArea_BaseUnit [PASS] NSUnitConcentrationMass_BaseUnit [PASS] NSUnitDispersion_BaseUnit [PASS] NSUnitDuration_BaseUnit [PASS] NSUnitElectricCharge_BaseUnit [PASS] NSUnitElectricCurrent_BaseUnit [PASS] NSUnitElectricPotentialDifference_BaseUnit [PASS] NSUnitElectricResistance_BaseUnit [PASS] NSUnitEnergy_BaseUnit [PASS] NSUnitFrequency_BaseUnit [PASS] NSUnitFuelEfficiency_BaseUnit [PASS] NSUnitIlluminance_BaseUnit [PASS] NSUnitLength_BaseUnit [PASS] NSUnitMass_BaseUnit [PASS] NSUnitPower_BaseUnit [PASS] NSUnitPressure_BaseUnit [PASS] NSUnitSpeed_BaseUnit [PASS] NSUnitTemperature_BaseUnit [PASS] NSUnitVolume_BaseUnit MonoTouchFixtures.Foundation.DimensionTest : 2.8877 ms MonoTouchFixtures.Foundation.EncodingDetectionOptionsTest [PASS] SetValueEnumArray MonoTouchFixtures.Foundation.EncodingDetectionOptionsTest : 0.3897 ms MonoTouchFixtures.Foundation.FileCoordinatorTest [PASS] CoordinateBatch_Action [PASS] CoordinateBatch_Action_Null [PASS] CoordinateRead [PASS] CoordinateRead_Null [PASS] CoordinateReadWrite [PASS] CoordinateReadWrite_Null [PASS] CoordinateWrite [PASS] CoordinateWrite_Null [PASS] CoordinateWriteWrite [PASS] CoordinateWriteWrite_Null MonoTouchFixtures.Foundation.FileCoordinatorTest : 11.177 ms MonoTouchFixtures.Foundation.FileHandleTest [PASS] Descriptor [PASS] DescriptorClose MonoTouchFixtures.Foundation.FileHandleTest : 0.2762 ms MonoTouchFixtures.Foundation.FormatterTests [PASS] DateGetObjectValueTest [PASS] DateIntervalStringFromDate [PASS] DateLocalizedStringFromDateComponents [PASS] DateStringForObjectValue [PASS] DateStringFromDate [PASS] DateStringFromDateComponents [PASS] DateStringFromTimeInterval [PASS] DateTestProperties [PASS] EnergyEnergyStringFromJoules [PASS] EnergyEnergyStringFromValue [PASS] EnergyGetObjectValue [PASS] EnergyUnitStringFromJoules [PASS] EnergyUnitStringFromValue MonoTouchFixtures.Foundation.FormatterTests : 4.9623 ms MonoTouchFixtures.Foundation.HttpCookieTest [PASS] CookieFromProperties [PASS] DotNetInterop_NonSession [PASS] DotNetInteropCommon [PASS] DotNetInteropMax [PASS] DotNetInteropMin [PASS] NiceFourCtor [PASS] NiceThreeCtor [PASS] NiceTwoCtor [PASS] NSDictionaryCtor [PASS] PortList_4990 MonoTouchFixtures.Foundation.HttpCookieTest : 2.5732 ms MonoTouchFixtures.Foundation.IndexPathTest [PASS] CompareTest [PASS] CreateTest [PASS] FromIndex [PASS] IndexAtPositionTest [PASS] IndexPathByAddingIndexTest [PASS] IndexPathByRemovingLastIndexTest MonoTouchFixtures.Foundation.IndexPathTest : 1.1349 ms MonoTouchFixtures.Foundation.KeyedUnarchiverTest [PASS] Exceptions MonoTouchFixtures.Foundation.KeyedUnarchiverTest : 0.5369 ms MonoTouchFixtures.Foundation.LocaleTest [PASS] CountryLessLocale [PASS] CurrentLocale [PASS] DisplayName_En [PASS] DisplayName_Fr [PASS] FromLocaleIdentifier [IGNORED] InitRegionInfo : Deprecated in xcode15 and does not longer return a valid value. at MonoTouchFixtures.Foundation.LocaleTest.InitRegionInfo() [PASS] Properties MonoTouchFixtures.Foundation.LocaleTest : 1.8162 ms MonoTouchFixtures.Foundation.MutableDataTest [PASS] Constructor Trying to allocate 2147483649 bytes, this may cause malloc errors [PASS] FromCapacity [PASS] FromLength MonoTouchFixtures.Foundation.MutableDataTest : 0.3796 ms MonoTouchFixtures.Foundation.MutableSAttributedStringTest [PASS] IndirectNullDictionary [PASS] InitWith [PASS] NullDictionary MonoTouchFixtures.Foundation.MutableSAttributedStringTest : 0.3283 ms MonoTouchFixtures.Foundation.MutableStringTest [PASS] Delete [PASS] Insert [PASS] Replace MonoTouchFixtures.Foundation.MutableStringTest : 0.4745 ms MonoTouchFixtures.Foundation.NetServiceTest [PASS] DefaultCtor MonoTouchFixtures.Foundation.NetServiceTest : 0.6709 ms MonoTouchFixtures.Foundation.NotificationCenterTest [IGNORED] Free : This test is 'randomly' failing the first time it's executed with debugging disabled (if executed with the rest of the tests) - CWLS show that the TestNotification instance is freed at the end of the test run. [PASS] TargetedNotificationsTest [PASS] ThreadSafe MonoTouchFixtures.Foundation.NotificationCenterTest : 104.4292 ms MonoTouchFixtures.Foundation.NotificationQueueTest [PASS] DefaultQueue MonoTouchFixtures.Foundation.NotificationQueueTest : 0.3988 ms MonoTouchFixtures.Foundation.NSArray1Test [PASS] Ctor [PASS] FromNSObjectsCountTest [PASS] FromNSObjectsNullTest [PASS] FromNSObjectsTest [PASS] IEnumerable1Test_EnumeratorType [PASS] IEnumerableTest [PASS] ToArray [PASS] ToArray_T MonoTouchFixtures.Foundation.NSArray1Test : 5.7521 ms MonoTouchFixtures.Foundation.NSArrayTest [PASS] Enumerator [PASS] Filter [PASS] FromNSObjects [PASS] FromStrings_Null [PASS] INativeObjects [PASS] Null [PASS] Sort [PASS] ToArray [PASS] ToArray_T MonoTouchFixtures.Foundation.NSArrayTest : 1.9977 ms MonoTouchFixtures.Foundation.NSAttributedStringDocumentAttributesTest [PASS] DefaultTabInterval [PASS] HyphenationFactor MonoTouchFixtures.Foundation.NSAttributedStringDocumentAttributesTest : 1.1196 ms MonoTouchFixtures.Foundation.NSCharacterSetTest [PASS] NSMutableCharacterSet_TestStaticSets MonoTouchFixtures.Foundation.NSCharacterSetTest : 1.1014 ms MonoTouchFixtures.Foundation.NSDataTest [PASS] AsStream [PASS] Base64_Long [PASS] Base64_Short [PASS] Base64Data [PASS] Base64String [PASS] BytesLength [PASS] ConstructorTest [PASS] FromEmptyArrayTest [PASS] FromFile [PASS] FromFile_Options [PASS] FromStream [PASS] FromStream_CanNotRead [PASS] FromStream_CanNotSeek [PASS] FromStream_Negative [PASS] FromStream_NoLength [PASS] FromStream_Position [PASS] FromString [IGNORED] Https : The site https://www.microsoft.com/robots.txt is not accessible. This test will be ignored. at MonoTests.System.Net.Http.NetworkResources.AssertNetworkConnection(String url) at MonoTests.System.Net.Http.NetworkResources.get_MicrosoftRobotsUrl() at MonoTests.System.Net.Http.NetworkResources.get_RobotsUrls() at MonoTouchFixtures.Foundation.NSDataTest.Https() [PASS] ToArray [PASS] ToEmptyArray [PASS] ToFromValueType [PASS] ToString_17693 MonoTouchFixtures.Foundation.NSDataTest : 227.571 ms MonoTouchFixtures.Foundation.NSDateComponentsTest [PASS] TestUndefinedComponent MonoTouchFixtures.Foundation.NSDateComponentsTest : 0.2425 ms MonoTouchFixtures.Foundation.NSDictionary2Test [PASS] ContainsKeyTest [PASS] Copy [PASS] Ctor [PASS] Ctor_Arrays [PASS] Ctor_NSDictionary [PASS] DictionaryCtorKeyValues [PASS] FromObjectsAndKeysGenericTest [PASS] ICollection2Test [PASS] IDictionary2Test [PASS] IEnumerable_KVP2Test [PASS] IEnumerableTest [PASS] InbalancedCtor [PASS] IndexerTest [PASS] InvalidType [PASS] KeysForObjectTest [PASS] KeysTest [IGNORED] KeyValue_Autorelease : RetainCount unusable for testing at MonoTouchFixtures.Foundation.NSDictionary2Test.KeyValue_Autorelease() [PASS] MutableCopy [PASS] ObjectForKeyTest [PASS] ObjectsForKeysTest [PASS] ToDictionaryTest [PASS] TryGetValueTest [PASS] ValuesTest [IGNORED] XForY_Autorelease : RetainCount unusable for testing at MonoTouchFixtures.Foundation.NSDictionary2Test.XForY_Autorelease() MonoTouchFixtures.Foundation.NSDictionary2Test : 8.4172 ms MonoTouchFixtures.Foundation.NSDictionaryTest [PASS] Copy [PASS] DictionaryCtorKeyValues [PASS] DictionaryCtorKeyValuesObjects [PASS] FromObjectsAndKeysTest 1 [PASS] InbalancedCtor [PASS] InbalancedCtor2 [PASS] IndexerTest [IGNORED] KeyValue_Autorelease : RetainCount unusable for testing at MonoTouchFixtures.Foundation.NSDictionaryTest.KeyValue_Autorelease() [PASS] MutableCopy [IGNORED] XForY_Autorelease : RetainCount unusable for testing at MonoTouchFixtures.Foundation.NSDictionaryTest.XForY_Autorelease() MonoTouchFixtures.Foundation.NSDictionaryTest : 2.1391 ms MonoTouchFixtures.Foundation.NSExpressionTest [PASS] AggregatePropertiesTest [PASS] AnyKeyPropertiesTest [PASS] BlockPropertiesTest [PASS] ConstantPropertiesTest [PASS] EvaluatedObjectPropertiesTest MonoTouchFixtures.Foundation.NSExpressionTest.FromConstant [PASS] FromConstant("Foo") [PASS] FromConstant(null) MonoTouchFixtures.Foundation.NSExpressionTest.FromConstant : 0.1272 ms [PASS] FromFormatConstant [PASS] FromFormatWithArgsTest [PASS] FromFormatWithNoArgsTest [PASS] FromFunctionTest [PASS] FromKeyPath [PASS] FunctionPropertiesTest [PASS] IntersectSetPropertiesTest [PASS] KeyPathPropertiesTest [PASS] MinusSetPropertiesTest [PASS] UnionSetPropertiesTest [PASS] VariablePropertiesTest MonoTouchFixtures.Foundation.NSExpressionTest : 5.5607 ms MonoTouchFixtures.Foundation.NSFileManagerTest [PASS] DefaultManager [IGNORED] DocumentDirectory : DocumentsDirectory and MyDocuments point to different locations on mac [PASS] GetHomeDirectoryForUserTest [PASS] GetHomeDirectoryTest [PASS] GetSkipBackupAttribute [PASS] GetUrlForUbiquityContainer : not iCloud enabled at MonoTouchFixtures.Foundation.NSFileManagerTest.GetUrlForUbiquityContainer() [PASS] GetUserFullNameTest [PASS] GetUserNameTest [PASS] LibraryDirectory [PASS] TemporaryDirectoryTest MonoTouchFixtures.Foundation.NSFileManagerTest : 9.3804 ms MonoTouchFixtures.Foundation.NSInputStreamTest [PASS] Data [PASS] Path [PASS] Read [PASS] SubclassedCtor [PASS] Url MonoTouchFixtures.Foundation.NSInputStreamTest : 1.0886 ms MonoTouchFixtures.Foundation.NSKeyedUnarchiverTest [PASS] DataTransformer_AllowedTopLevelTypes_WrapperTests [PASS] GetUnarchivedObject_TypeWrappers MonoTouchFixtures.Foundation.NSKeyedUnarchiverTest : 1.1345 ms MonoTouchFixtures.Foundation.NSLayoutConstraintTest [PASS] FromVisualFormat MonoTouchFixtures.Foundation.NSLayoutConstraintTest : 2.415 ms MonoTouchFixtures.Foundation.NSLocaleTest [PASS] GetNSObject_IntPtrZero MonoTouchFixtures.Foundation.NSLocaleTest : 0.0429 ms MonoTouchFixtures.Foundation.NSMutableArray1Test [PASS] AddObjectsTest [PASS] AddTest [PASS] ContainsTest [PASS] Ctor [PASS] Ctor_Capacity [PASS] IEnumerable1Test [PASS] IEnumerable1Test_EnumeratorType [PASS] IEnumerableTest [PASS] IndexerTest [PASS] IndexOfTest [PASS] InsertObjectsTest [PASS] InsertTest [PASS] ReplaceObjectTest MonoTouchFixtures.Foundation.NSMutableArray1Test : 8.7587 ms MonoTouchFixtures.Foundation.NSMutableDictionary2Test [PASS] AddEntries [PASS] AddTest [PASS] ContainsKeyTest [PASS] Copy [PASS] Ctor [PASS] Ctor_NSDictionary [PASS] Ctor_NSMutableDictionary [PASS] FromObjectsAndKeysGenericTest [PASS] ICollection2Test [PASS] IDictionary2Test [PASS] IEnumerable_KVP2Test [PASS] IEnumerableTest [PASS] IndexerTest [PASS] InvalidType [PASS] KeysForObjectTest [PASS] KeysTest [IGNORED] KeyValue_Autorelease : RetainCount unusable for testing at MonoTouchFixtures.Foundation.NSMutableDictionary2Test.KeyValue_Autorelease() [PASS] MutableCopy [PASS] ObjectForKeyTest [PASS] ObjectsForKeysTest [PASS] RemoveTest [PASS] TryGetValueTest [PASS] ValuesTest [IGNORED] XForY_Autorelease : RetainCount unusable for testing at MonoTouchFixtures.Foundation.NSMutableDictionary2Test.XForY_Autorelease() MonoTouchFixtures.Foundation.NSMutableDictionary2Test : 7.8913 ms MonoTouchFixtures.Foundation.NSMutableOrderedSet1Test [PASS] AddObjectsTest [PASS] AddTest [PASS] AsSetTest [PASS] Ctor [PASS] Ctor_Capacity [PASS] Ctor_NSMutableOrderedSet [PASS] Ctor_NSOrderedSet [PASS] Ctor_NSSet [PASS] Ctor_Params [PASS] Ctor_Start [PASS] IEnumerable1Test [PASS] IEnumerable1Test_EnumeratorType [PASS] IEnumerableTest [PASS] IndexerTest [PASS] InsertObjectsTest [PASS] InsertTest [PASS] OperatorAddTest [PASS] OperatorAddTest2 [PASS] OperatorAddTest3 [PASS] OperatorPlusReferenceTest [PASS] OperatorSubtractTest [PASS] OperatorSubtractTest2 [PASS] OperatorSubtractTest3 [PASS] RemoveObjectsTest [PASS] RemoveObjectTest [PASS] ReplaceObjectsTest [PASS] ReplaceTest MonoTouchFixtures.Foundation.NSMutableOrderedSet1Test : 12.8998 ms MonoTouchFixtures.Foundation.NSMutableOrderedSetTest [PASS] OperatorAddTest [PASS] OperatorDifferentTest [PASS] OperatorEqualTest [PASS] OperatorPlusReferenceTest [PASS] OperatorSubtractTest MonoTouchFixtures.Foundation.NSMutableOrderedSetTest : 1.0461 ms MonoTouchFixtures.Foundation.NSMutableSet1Test [PASS] AddObjectsTest [PASS] AddTest [PASS] AnyObjectTest [PASS] ContainsTest [PASS] Ctor [PASS] Ctor_OtherMutableSet [PASS] Ctor_OtherSet [PASS] Ctor_Params [PASS] IEnumerable1Test [PASS] IEnumerable1Test_EnumeratorType [PASS] IEnumerableTest [PASS] LookupMemberTest [PASS] OperatorAddTest [PASS] OperatorPlusReferenceTest [PASS] OperatorSubtractTest [PASS] RemoveTest [PASS] ToArrayTest MonoTouchFixtures.Foundation.NSMutableSet1Test : 9.1816 ms MonoTouchFixtures.Foundation.NSMutableSetTest [PASS] OperatorAddTest [PASS] OperatorPlusReferenceTest [PASS] OperatorSubtractTest MonoTouchFixtures.Foundation.NSMutableSetTest : 0.5974 ms MonoTouchFixtures.Foundation.NSObjectTest [PASS] Copy [PASS] Encode [PASS] Equality [PASS] FromObject_Handle [PASS] FromObject_INativeObject [PASS] FromObject_NativeTypes [PASS] InvokeTest [PASS] IsDirectBinding [PASS] ObserverTest [PASS] SubclassEquality [PASS] SuperClass [PASS] ValueForInvalidKeyTest MonoTouchFixtures.Foundation.NSObjectTest : 204.0888 ms MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionCompare [PASS] NSVersionCompare((1.0.0, 1.0.0, 0)) [PASS] NSVersionCompare((1.0.0, 2.0.0, -1)) [PASS] NSVersionCompare((1.1.0, 1.0.0, 1)) [PASS] NSVersionCompare((1.2.2, 1.2.3, -1)) MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionCompare : 0.1402 ms MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionCompareObject [PASS] NSVersionCompareObject((1.2.3, 0)) [PASS] NSVersionCompareObject((2.0.0, -1)) [PASS] NSVersionCompareObject((hello, 1)) [PASS] NSVersionCompareObject((, 1)) MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionCompareObject : 0.1157 ms MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionEqual [PASS] NSVersionEqual((1.0.0, 1.0.0, True)) [PASS] NSVersionEqual((1.0.0, 2.0.0, False)) [PASS] NSVersionEqual((1.1.0, 1.0.0, False)) [PASS] NSVersionEqual((1.0.0, 1.0.0, True)) [PASS] NSVersionEqual((1.1.2, 1.2.3, False)) [PASS] NSVersionEqual((1.2.3, 1.2.3, True)) [PASS] NSVersionEqual((1.2.0, 1.2.0, True)) MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionEqual : 0.3959 ms MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionHashCode [PASS] NSVersionHashCode(1.2.3) [PASS] NSVersionHashCode(2.0.0) [PASS] NSVersionHashCode(2.5.0) [PASS] NSVersionHashCode(2.9.10) MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionHashCode : 0.1755 ms MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionObject [PASS] NSVersionObject((1.2.3, True)) [PASS] NSVersionObject((2.0.0, False)) [PASS] NSVersionObject((hello, False)) [PASS] NSVersionObject((, False)) MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionObject : 0.1368 ms MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionToString [PASS] NSVersionToString((1.2.3, 1.2.3)) [PASS] NSVersionToString((2.0.0, 2.0.0)) [PASS] NSVersionToString((2.5.0, 2.5.0)) [PASS] NSVersionToString((2.9.10, 2.9.10)) MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest.NSVersionToString : 0.1087 ms MonoTouchFixtures.Foundation.NSOperatingSystemVersionTest : 1.1282 ms MonoTouchFixtures.Foundation.NSOrderedSet1Test [PASS] AsSetTest [PASS] ContainsTest [PASS] Ctor [PASS] Ctor_NSMutableOrderedSet [PASS] Ctor_NSOrderedSet [PASS] Ctor_NSSet [PASS] Ctor_Params [PASS] Ctor_Start [PASS] FirstObjectTest [PASS] IEnumerable1Test [PASS] IEnumerable1Test_EnumeratorType [PASS] IEnumerableTest [PASS] IndexerTest [PASS] IndexOfTest [PASS] LastObjectTest [PASS] OperatorAddTest [PASS] OperatorAddTest2 [PASS] OperatorDifferentTest [PASS] OperatorEqualTest [PASS] OperatorPlusReferenceTest [PASS] OperatorSubtractTest [PASS] OperatorSubtractTest2 [PASS] ToArrayTest MonoTouchFixtures.Foundation.NSOrderedSet1Test : 14.7942 ms MonoTouchFixtures.Foundation.NSOrderedSetTest [PASS] OperatorAddTest [PASS] OperatorAddTest2 [PASS] OperatorAddTest3 [PASS] OperatorDifferentTest [PASS] OperatorEqualTest [PASS] OperatorPlusReferenceTest [PASS] OperatorSubtractTest [PASS] OperatorSubtractTest2 [PASS] OperatorSubtractTest3 MonoTouchFixtures.Foundation.NSOrderedSetTest : 2.0002 ms MonoTouchFixtures.Foundation.NSRangeTest MonoTouchFixtures.Foundation.NSRangeTest.IEquatableImplementation [PASS] IEquatableImplementation(1,1,1,1,True) [PASS] IEquatableImplementation(2,1,1,1,False) [PASS] IEquatableImplementation(1,2,1,1,False) [PASS] IEquatableImplementation(1,1,2,1,False) [PASS] IEquatableImplementation(1,1,1,2,False) MonoTouchFixtures.Foundation.NSRangeTest.IEquatableImplementation : 0.2548 ms MonoTouchFixtures.Foundation.NSRangeTest : 0.2829 ms MonoTouchFixtures.Foundation.NSSet1Test [PASS] AnyObjectTest [PASS] ContainsTest [PASS] CreateTest [PASS] Ctor [PASS] Ctor_OtherMutableSet [PASS] Ctor_OtherSet [PASS] Ctor_Params [PASS] IEnumerable1Test [PASS] IEnumerable1Test_EnumeratorType [PASS] IEnumerableTest [PASS] LookupMemberTest [PASS] OperatorAddTest [PASS] OperatorPlusReferenceTest [PASS] OperatorSubtractTest [PASS] ToArrayTest [PASS] ToHashSetTest MonoTouchFixtures.Foundation.NSSet1Test : 12.7289 ms MonoTouchFixtures.Foundation.NSSetTest [PASS] OperatorAddTest [PASS] OperatorAddTest2 [PASS] OperatorPlus [PASS] OperatorPlusReferenceTest [PASS] OperatorSubtractTest [PASS] OperatorSubtractTest2 [PASS] SetCtors MonoTouchFixtures.Foundation.NSSetTest : 2.769 ms MonoTouchFixtures.Foundation.NSStreamTest [PASS] BoundPairTest [PASS] ConnectToHost [PASS] ConnectToPeer MonoTouchFixtures.Foundation.NSStreamTest : 3.4756 ms MonoTouchFixtures.Foundation.NSTextListTest MonoTouchFixtures.Foundation.NSTextListTest.Constructor_CustomFormat [PASS] Constructor_CustomFormat("{decimal}.") [PASS] Constructor_CustomFormat("•") MonoTouchFixtures.Foundation.NSTextListTest.Constructor_CustomFormat : 4.0036 ms MonoTouchFixtures.Foundation.NSTextListTest.Constructor_CustomFormat_2 [PASS] Constructor_CustomFormat_2("{decimal}.",None) [PASS] Constructor_CustomFormat_2("•",PrependEnclosingMarker) MonoTouchFixtures.Foundation.NSTextListTest.Constructor_CustomFormat_2 : 0.3134 ms MonoTouchFixtures.Foundation.NSTextListTest.Constructor_TypedFormat [PASS] Constructor_TypedFormat(Circle) [PASS] Constructor_TypedFormat(Diamond) MonoTouchFixtures.Foundation.NSTextListTest.Constructor_TypedFormat : 0.2407 ms MonoTouchFixtures.Foundation.NSTextListTest.Constructor_TypedFormat_2 [PASS] Constructor_TypedFormat_2(Check,None) [PASS] Constructor_TypedFormat_2(Box,PrependEnclosingMarker) MonoTouchFixtures.Foundation.NSTextListTest.Constructor_TypedFormat_2 : 0.2324 ms MonoTouchFixtures.Foundation.NSTextListTest : 4.8702 ms MonoTouchFixtures.Foundation.NSTimeZoneTest [PASS] AbbreviationsTest [PASS] AbbreviationTest [PASS] All_28300 [PASS] KnownTimeZoneNames MonoTouchFixtures.Foundation.NSTimeZoneTest : 88.0477 ms MonoTouchFixtures.Foundation.NSUbiquitousKeyValueStoreTest [PASS] Indexer MonoTouchFixtures.Foundation.NSUbiquitousKeyValueStoreTest : 0.9135 ms MonoTouchFixtures.Foundation.NSUrlSessionConfigurationTest [PASS] TestSessionTypeBackground [PASS] TestSessionTypeDefault [PASS] TestSessionTypeEphemeral MonoTouchFixtures.Foundation.NSUrlSessionConfigurationTest : 0.3448 ms MonoTouchFixtures.Foundation.NumberTest [PASS] CompareTo [PASS] CtorNSCoder [PASS] Equals [PASS] Singleton MonoTouchFixtures.Foundation.NumberTest : 0.5563 ms MonoTouchFixtures.Foundation.OperationQueueTest [PASS] Add_NSAction_Null [PASS] Add_NSOperation_Null [PASS] Add_NSOperations_Null MonoTouchFixtures.Foundation.OperationQueueTest : 0.3769 ms MonoTouchFixtures.Foundation.OutputStreamTest [PASS] Memory [PASS] Path [PASS] Write MonoTouchFixtures.Foundation.OutputStreamTest : 0.6358 ms MonoTouchFixtures.Foundation.RegularExpressionTest [PASS] GetMatches MonoTouchFixtures.Foundation.RegularExpressionTest : 3.4755 ms MonoTouchFixtures.Foundation.StringTest [PASS] Compare [PASS] Compare_Locale [PASS] Compare_Null [PASS] Compare_Options [PASS] Compare_Range [PASS] DrawingExtensions [PASS] Equality [PASS] FromData [PASS] PathExtensions [PASS] ReleaseEmptyString [PASS] Replace_Range MonoTouchFixtures.Foundation.StringTest : 2.849 ms MonoTouchFixtures.Foundation.ThreadTest [PASS] GetEntryAssemblyReturnsOk [PASS] InitWithDataTest [PASS] MainThread MonoTouchFixtures.Foundation.ThreadTest : 8.0154 ms MonoTouchFixtures.Foundation.TimerTest [PASS] Bug17793 Thread started Thread timer added Thread ended [PASS] Bug2443 [PASS] CreateTimer_NewSignature MonoTouchFixtures.Foundation.TimerTest : 503.9645 ms MonoTouchFixtures.Foundation.UrlConnectionTest [PASS] SendSynchronousRequest [PASS] StartCancel MonoTouchFixtures.Foundation.UrlConnectionTest : 195.7133 ms MonoTouchFixtures.Foundation.UrlCredentialTest [PASS] Ctor_Trust [PASS] FromTrust MonoTouchFixtures.Foundation.UrlCredentialTest : 1.5901 ms MonoTouchFixtures.Foundation.UrlProtectionSpaceTest [PASS] Http [PASS] HttpProxy [PASS] HttpProxy_Proxy [PASS] Https MonoTouchFixtures.Foundation.UrlProtectionSpaceTest : 1.5609 ms MonoTouchFixtures.Foundation.UrlProtocolTest [PASS] RegistrarTest [PASS] Registration MonoTouchFixtures.Foundation.UrlProtocolTest : 103.181 ms MonoTouchFixtures.Foundation.UrlRequestTest [PASS] Mutability_30744 MonoTouchFixtures.Foundation.UrlRequestTest : 0.7077 ms MonoTouchFixtures.Foundation.UrlSessionConfigurationTest [PASS] BackgroundSessionConfiguration [PASS] Default_Properties MonoTouchFixtures.Foundation.UrlSessionConfigurationTest : 1.0032 ms MonoTouchFixtures.Foundation.UrlSessionTaskMetricsTest [PASS] Properties MonoTouchFixtures.Foundation.UrlSessionTaskMetricsTest : 0.5017 ms MonoTouchFixtures.Foundation.UrlSessionTaskTest [PASS] NSUrlSessionDataTaskTest [PASS] NSUrlSessionDownloadTaskTest [PASS] NSUrlSessionUploadTaskTest [PASS] Properties MonoTouchFixtures.Foundation.UrlSessionTaskTest : 4.5306 ms MonoTouchFixtures.Foundation.UrlSessionTaskTransactionMetricsTest [PASS] Properties MonoTouchFixtures.Foundation.UrlSessionTaskTransactionMetricsTest : 0.3184 ms MonoTouchFixtures.Foundation.UrlSessionTest [PASS] CreateDataTaskAsync [PASS] DownloadDataAsync [PASS] SharedSession MonoTouchFixtures.Foundation.UrlSessionTest : 2839.4412 ms MonoTouchFixtures.Foundation.UrlTest [PASS] Bug13069 [PASS] Copy [PASS] Ctor_string [PASS] Equals [PASS] Fields [PASS] FromNullString [PASS] FromString MonoTouchFixtures.Foundation.UrlTest.ImplicitOperatorRoundTrip [PASS] ImplicitOperatorRoundTrip("http://microsoft.com/",Absolute) [PASS] ImplicitOperatorRoundTrip("https://microsoft.com/",Absolute) [PASS] ImplicitOperatorRoundTrip("https://microsoft.com/some/path",Absolute) [PASS] ImplicitOperatorRoundTrip("https://microsoft.com/page?value=foo",Absolute) [PASS] ImplicitOperatorRoundTrip("relative",Relative) [PASS] ImplicitOperatorRoundTrip("relative/to/some/page",Relative) [PASS] ImplicitOperatorRoundTrip("relative?value=foo",Relative) MonoTouchFixtures.Foundation.UrlTest.ImplicitOperatorRoundTrip : 1.785 ms [PASS] InitWithSpaces [PASS] Invalid_29510 [PASS] IsExcludedFromBackupKey [PASS] MutableCopy [PASS] SubclassEquality [PASS] TestEqualOperator [PASS] TestEqualOperatorNull [PASS] TestEqualOperatorSameInstace [PASS] TestNotEqualOperator [PASS] TestNotEqualOperatorNull [PASS] Unicode_6597 MonoTouchFixtures.Foundation.UrlTest : 10.1458 ms MonoTouchFixtures.Foundation.UserDefaultsTest [PASS] Ctor_SuiteName [PASS] Ctor_UserName [PASS] SetString MonoTouchFixtures.Foundation.UserDefaultsTest : 3.371 ms MonoTouchFixtures.Foundation.UuidTest [PASS] ConstructorFailures [PASS] Constructors MonoTouchFixtures.Foundation.UuidTest : 0.5091 ms MonoTouchFixtures.Foundation.ZoneTest [PASS] Default MonoTouchFixtures.Foundation.ZoneTest : 0.2198 ms MonoTouchFixtures.Foundation : 4495.7105 ms MonoTouchFixtures.GameController MonoTouchFixtures.GameController.ExtendedGamepadSnapshotTest [PASS] Nullability MonoTouchFixtures.GameController.ExtendedGamepadSnapshotTest : 0.4194 ms MonoTouchFixtures.GameController.GamepadSnapshotTest [PASS] Nullability MonoTouchFixtures.GameController.GamepadSnapshotTest : 0.246 ms MonoTouchFixtures.GameController.GCControllerTest [IGNORED] TheTest : No controllers! at MonoTouchFixtures.GameController.GCControllerTest.TheTest() MonoTouchFixtures.GameController.GCControllerTest : 5.7434 ms MonoTouchFixtures.GameController.GCInputTest [PASS] ButtonNames MonoTouchFixtures.GameController.GCInputTest : 0.3665 ms MonoTouchFixtures.GameController.GCPoint2Test [PASS] TheTest MonoTouchFixtures.GameController.GCPoint2Test : 1.3515 ms MonoTouchFixtures.GameController : 8.1616 ms MonoTouchFixtures.GameKit MonoTouchFixtures.GameKit.GKGameCenterViewControllerTest [PASS] StringCtor [PASS] StringOptionCtor_AchievementId [PASS] StringOptionCtor_LeaderboardSetId MonoTouchFixtures.GameKit.GKGameCenterViewControllerTest : 1.1897 ms MonoTouchFixtures.GameKit.LeaderboardTest [PASS] DefaultCtor [PASS] PlayersCtor MonoTouchFixtures.GameKit.LeaderboardTest : 1.1779 ms MonoTouchFixtures.GameKit.NotificationBannerTest [PASS] Show_NSAction_Null MonoTouchFixtures.GameKit.NotificationBannerTest : 6.345 ms MonoTouchFixtures.GameKit.ScoreTest [PASS] Ctor_String MonoTouchFixtures.GameKit.ScoreTest : 8.0306 ms MonoTouchFixtures.GameKit.SessionTest [PASS] NullAllowed MonoTouchFixtures.GameKit.SessionTest : 1.7436 ms MonoTouchFixtures.GameKit : 18.5244 ms MonoTouchFixtures.GameplayKit MonoTouchFixtures.GameplayKit.GKStateMachineTests [PASS] StateMachineTests MonoTouchFixtures.GameplayKit.GKStateMachineTests : 0.6745 ms MonoTouchFixtures.GameplayKit.GKStateTests [PASS] Concrete [PASS] IsValidNextState [PASS] NullIsValidNextState MonoTouchFixtures.GameplayKit.GKStateTests : 0.4659 ms MonoTouchFixtures.GameplayKit : 1.1491 ms MonoTouchFixtures.GamePlayKit MonoTouchFixtures.GamePlayKit.GKAgent3DTest MonoTouchFixtures.GamePlayKit.GKAgent3DTest : 0.001 ms MonoTouchFixtures.GamePlayKit.GKBehaviorTests [PASS] ObjectForKeyedSubscriptTest MonoTouchFixtures.GamePlayKit.GKBehaviorTests : 0.3737 ms MonoTouchFixtures.GamePlayKit.GKComponentSystemTests [PASS] IndexerTest [PASS] InitWithComponentClassType MonoTouchFixtures.GamePlayKit.GKComponentSystemTests : 0.7382 ms MonoTouchFixtures.GamePlayKit.GKEntityTests [PASS] BadGetComponent [PASS] BadRemoval [PASS] GetAndRemoveTest MonoTouchFixtures.GamePlayKit.GKEntityTests : 0.5747 ms MonoTouchFixtures.GamePlayKit.GKGridGraphTests [PASS] FromGridStartingAtTest [PASS] InitFromGridStartingAtTest MonoTouchFixtures.GamePlayKit.GKGridGraphTests : 4.4354 ms MonoTouchFixtures.GamePlayKit.GKMeshGraphTests [PASS] GKTriangleTest MonoTouchFixtures.GamePlayKit.GKMeshGraphTests : 0.6887 ms MonoTouchFixtures.GamePlayKit.GKNoiseMapTests [PASS] Vector2dTest MonoTouchFixtures.GamePlayKit.GKNoiseMapTests : 0.9223 ms MonoTouchFixtures.GamePlayKit.GKNoiseTests [PASS] Vector3dTest MonoTouchFixtures.GamePlayKit.GKNoiseTests : 0.3309 ms MonoTouchFixtures.GamePlayKit.GKOctreeTests [PASS] GKBoxTest MonoTouchFixtures.GamePlayKit.GKOctreeTests : 0.409 ms MonoTouchFixtures.GamePlayKit.GKPathTests [PASS] FromPointsTest [PASS] FromPointsVector3Test [PASS] InitWithPointsTest [PASS] InitWithPointsVector3Test MonoTouchFixtures.GamePlayKit.GKPathTests : 0.9859 ms MonoTouchFixtures.GamePlayKit.GKPolygonObstacleTests [PASS] FromPointsTest [PASS] InitWithPointsTest MonoTouchFixtures.GamePlayKit.GKPolygonObstacleTests : 0.3697 ms MonoTouchFixtures.GamePlayKit.GKQuadTreeTests [PASS] GKQuadTest MonoTouchFixtures.GamePlayKit.GKQuadTreeTests : 0.305 ms MonoTouchFixtures.GamePlayKit : 10.2924 ms MonoTouchFixtures.HealthKit MonoTouchFixtures.HealthKit.AnchoredObjectQueryTest [PASS] NoAnchor MonoTouchFixtures.HealthKit.AnchoredObjectQueryTest : 0.53 ms MonoTouchFixtures.HealthKit.CategoryTypeIdentifier [PASS] EnumValues_22351 MonoTouchFixtures.HealthKit.CategoryTypeIdentifier : 4.4747 ms MonoTouchFixtures.HealthKit.CdaDocumentSampleTest [PASS] Error MonoTouchFixtures.HealthKit.CdaDocumentSampleTest : 84.2159 ms MonoTouchFixtures.HealthKit.ErrorTest [PASS] Domain MonoTouchFixtures.HealthKit.ErrorTest : 0.1887 ms MonoTouchFixtures.HealthKit.HKAppleSleepingBreathingDisturbancesTest [PASS] RoundtripTest MonoTouchFixtures.HealthKit.HKAppleSleepingBreathingDisturbancesTest : 0.3938 ms MonoTouchFixtures.HealthKit.HKAppleWalkingSteadinessTest [IGNORED] GetMaximumQuantityTest : This test does not run on Desktops (macOS or MacCatalyst). at MonoTouchFixtures.HealthKit.HKAppleWalkingSteadinessTest.SetUp() [IGNORED] GetMinimumQuantityTest : This test does not run on Desktops (macOS or MacCatalyst). at MonoTouchFixtures.HealthKit.HKAppleWalkingSteadinessTest.SetUp() [IGNORED] TryGetClassificationTest : This test does not run on Desktops (macOS or MacCatalyst). at MonoTouchFixtures.HealthKit.HKAppleWalkingSteadinessTest.SetUp() MonoTouchFixtures.HealthKit.HKAppleWalkingSteadinessTest : 0.2948 ms MonoTouchFixtures.HealthKit.HKCategoryValueSleepAnalysisTest [PASS] GetAsleepValuesTest MonoTouchFixtures.HealthKit.HKCategoryValueSleepAnalysisTest : 0.4299 ms MonoTouchFixtures.HealthKit.HKHealthStoreTest [PASS] GetBiologicalSexNullReturnTest [PASS] GetBloodTypeNullReturnTest MonoTouchFixtures.HealthKit.HKHealthStoreTest : 0.5703 ms MonoTouchFixtures.HealthKit.HKWorkoutBuilderTest [PASS] GetSeriesBuilderNullReturnTest MonoTouchFixtures.HealthKit.HKWorkoutBuilderTest : 0.5933 ms MonoTouchFixtures.HealthKit.ObjectTypeTest [PASS] Workout MonoTouchFixtures.HealthKit.ObjectTypeTest : 0.1667 ms MonoTouchFixtures.HealthKit.QuantityTypeIdentifier [PASS] EnumValues_22351 MonoTouchFixtures.HealthKit.QuantityTypeIdentifier : 5.3712 ms MonoTouchFixtures.HealthKit : 97.2889 ms MonoTouchFixtures.HomeKit MonoTouchFixtures.HomeKit.HMCharacteristicTest [PASS] WriteValueNullTest MonoTouchFixtures.HomeKit.HMCharacteristicTest : 0.2342 ms MonoTouchFixtures.HomeKit.HMMutableSignificantTimeEventTest [PASS] SignificantEventPropertyTest MonoTouchFixtures.HomeKit.HMMutableSignificantTimeEventTest : 0.447 ms MonoTouchFixtures.HomeKit.HMSignificantTimeEventTest [PASS] SignificantEventPropertyTest MonoTouchFixtures.HomeKit.HMSignificantTimeEventTest : 0.1054 ms MonoTouchFixtures.HomeKit : 0.7967 ms MonoTouchFixtures.HttpClientTests MonoTouchFixtures.HttpClientTests.HttpClientTest MonoTouchFixtures.HttpClientTests.HttpClientTest.EnsureModifiabilityPostSend [PASS] EnsureModifiabilityPostSend(System.Net.Http.HttpClientHandler,8) [PASS] EnsureModifiabilityPostSend(System.Net.Http.CFNetworkHandler,8) [PASS] EnsureModifiabilityPostSend(System.Net.Http.NSUrlSessionHandler,9) MonoTouchFixtures.HttpClientTests.HttpClientTest.EnsureModifiabilityPostSend : 1.1276 ms MonoTouchFixtures.HttpClientTests.HttpClientTest : 1.1437 ms MonoTouchFixtures.HttpClientTests : 1.1488 ms MonoTouchFixtures.ImageIO MonoTouchFixtures.ImageIO.CGImageAnimationTest [PASS] AnimateImageWithData [PASS] AnimateImageWithDataChangeHandler [PASS] AnimateImageWithDataNullData [PASS] AnimateImageWithDataNullHandler [PASS] AnimateImageWithUrl [PASS] AnimateImageWithUrlChangeHandler [PASS] AnimateImageWithUrlNullHandler [PASS] AnimateImageWithUrlNullUrl MonoTouchFixtures.ImageIO.CGImageAnimationTest : 613.7951 ms MonoTouchFixtures.ImageIO.CGImageSourceTest [PASS] CopyMetadata [PASS] CopyProperties [PASS] CreateImageTest [PASS] CreateIncrementalTest [PASS] CreateThumbnailTest [PASS] FromDataProviderTest [PASS] FromDataTest [PASS] FromUrlTest [PASS] GetProperties [PASS] RemoveCache MonoTouchFixtures.ImageIO.CGImageSourceTest : 13.7956 ms MonoTouchFixtures.ImageIO.ImageDestinationTest [PASS] AddImage [PASS] AddImageAndMetadata [PASS] CopyImageSource [PASS] Create_DataConsumer_BadUTI [PASS] Create_DataConsumer_GoodUTI [PASS] FromData_BadITU [PASS] FromData_GoodITU [PASS] FromUrl_BadITU [PASS] TypeIdentifiers MonoTouchFixtures.ImageIO.ImageDestinationTest : 3.9161 ms MonoTouchFixtures.ImageIO.ImageMetadataTagTest [PASS] Ctor_Bool_False [PASS] Ctor_Bool_True [PASS] Ctor_NSArray [PASS] Ctor_NSDictionary [PASS] Ctor_NSNumber [PASS] Ctor_NSString [PASS] Ctor_Null MonoTouchFixtures.ImageIO.ImageMetadataTagTest : 2.0837 ms MonoTouchFixtures.ImageIO.ImageMetadataTest [PASS] Defaults MonoTouchFixtures.ImageIO.ImageMetadataTest : 1.3793 ms MonoTouchFixtures.ImageIO.MutableImageMetadataTest [PASS] Defaults ImageIO.CGMutableImageMetadata MonoTouchFixtures.ImageIO.MutableImageMetadataTest : 0.7962 ms MonoTouchFixtures.ImageIO : 635.8298 ms MonoTouchFixtures.Intents MonoTouchFixtures.Intents.INIntentResolutionResultTests [PASS] INBillPayeeResolutionResultPropertyTest [PASS] INBillTypeResolutionResultPropertyTest [PASS] INBooleanResolutionResultPropertyTest [PASS] INCallRecordTypeResolutionResultPropertyTest [PASS] INCarAirCirculationModeResolutionResultPropertyTest [PASS] INCarAudioSourceResolutionResultPropertyTest [PASS] INCarDefrosterResolutionResultPropertyTest [PASS] INCarSeatResolutionResultPropertyTest [PASS] INCarSignalOptionsResolutionResultPropertyTest [PASS] INCurrencyAmountResolutionResultPropertyTest [PASS] INDateComponentsRangeResolutionResultPropertyTest [PASS] INDateComponentsResolutionResultPropertyTest [PASS] INDoubleResolutionResultPropertyTest [PASS] INIntegerResolutionResultPropertyTest [PASS] INIntentResolutionResultIsAbstractTest [PASS] INMessageAttributeOptionsResolutionResultPropertyTest [PASS] INMessageAttributeResolutionResultPropertyTest [PASS] INPaymentAccountResolutionResultPropertyTest [PASS] INPaymentAmountResolutionResultPropertyTest [PASS] INPaymentStatusResolutionResultPropertyTest [PASS] INPersonResolutionResultPropertyTest [PASS] INPlacemarkResolutionResultPropertyTest [PASS] INRadioTypeResolutionResultPropertyTest [PASS] INRelativeReferenceResolutionResultPropertyTest [PASS] INRelativeSettingResolutionResultPropertyTest [PASS] INRestaurantGuestResolutionResultPropertyTest [PASS] INRestaurantResolutionResultPropertyTest [PASS] INSpeakableStringResolutionResultPropertyTest [PASS] INStringResolutionResultPropertyTest [PASS] INTemperatureResolutionResultPropertyTest [PASS] INWorkoutGoalUnitTypeResolutionResultPropertyTest [PASS] INWorkoutLocationTypeResolutionResultPropertyTest MonoTouchFixtures.Intents.INIntentResolutionResultTests : 9.6522 ms MonoTouchFixtures.Intents : 9.6632 ms MonoTouchFixtures.JavascriptCore MonoTouchFixtures.JavascriptCore.ContextTest [PASS] EvaluateScript [PASS] EvaluateScript_Context [PASS] EvaluateScript_Param MonoTouchFixtures.JavascriptCore.ContextTest : 5.2213 ms MonoTouchFixtures.JavascriptCore.JSExportTest [PASS] ExportTest MonoTouchFixtures.JavascriptCore.JSExportTest : 1.9516 ms MonoTouchFixtures.JavascriptCore.ValueTest [PASS] CreatePromise [PASS] From [PASS] Invoke [PASS] IsEqual [PASS] ToArray MonoTouchFixtures.JavascriptCore.ValueTest : 5.5015 ms MonoTouchFixtures.JavascriptCore : 12.7191 ms MonoTouchFixtures.LocalAuthentication MonoTouchFixtures.LocalAuthentication.LADomainStateCompanionTest [PASS] AvailableCompanionTypes MonoTouchFixtures.LocalAuthentication.LADomainStateCompanionTest : 27.5366 ms MonoTouchFixtures.LocalAuthentication : 27.5419 ms MonoTouchFixtures.MapKit MonoTouchFixtures.MapKit.AddressFilterTest [PASS] Constructors MonoTouchFixtures.MapKit.AddressFilterTest : 0.1887 ms MonoTouchFixtures.MapKit.AnnotationViewTest [PASS] Default [PASS] InitWithAnnotation [PASS] InitWithFrame [PASS] Null MonoTouchFixtures.MapKit.AnnotationViewTest : 2.8924 ms MonoTouchFixtures.MapKit.CircleViewTest [PASS] InitWithFrame MonoTouchFixtures.MapKit.CircleViewTest : 0.1943 ms MonoTouchFixtures.MapKit.GeometryTest [PASS] MapPointsPerMeterAtLatitude [PASS] MetersBetweenMapPoints [PASS] MetersPerMapPointAtLatitude MonoTouchFixtures.MapKit.GeometryTest : 0.3109 ms MonoTouchFixtures.MapKit.LocalSearchRequestTest [PASS] Default MonoTouchFixtures.MapKit.LocalSearchRequestTest : 0.1365 ms MonoTouchFixtures.MapKit.LocalSearchTest [PASS] EmptyRequest MonoTouchFixtures.MapKit.LocalSearchTest : 2007.2127 ms MonoTouchFixtures.MapKit.MapRectTest [PASS] ContainsPoint [PASS] ContainsRect [PASS] Defaults [PASS] Divide [PASS] Inset [PASS] Intersection [PASS] NullRect [PASS] Offset [PASS] Remainder [PASS] Union MonoTouchFixtures.MapKit.MapRectTest : 3.9742 ms MonoTouchFixtures.MapKit.MapViewTest [PASS] InitWithFrame [PASS] Overlays [PASS] Overlays7 MonoTouchFixtures.MapKit.MapViewTest : 84.6416 ms MonoTouchFixtures.MapKit.OverlayPathRendererTest [PASS] CtorOverlay [PASS] DefaultCtor MonoTouchFixtures.MapKit.OverlayPathRendererTest : 0.447 ms MonoTouchFixtures.MapKit.OverlayPathViewTest [PASS] InitWithFrame MonoTouchFixtures.MapKit.OverlayPathViewTest : 0.1971 ms MonoTouchFixtures.MapKit.OverlayViewTest [PASS] InitWithFrame MonoTouchFixtures.MapKit.OverlayViewTest : 0.1901 ms MonoTouchFixtures.MapKit.PinAnnotationViewTest [PASS] Ctor_Annotation [PASS] InitWithFrame MonoTouchFixtures.MapKit.PinAnnotationViewTest : 3.431 ms MonoTouchFixtures.MapKit.PolygonTest [PASS] FromCoordinates_Empty [PASS] FromCoordinates_Interior_Empty [PASS] FromCoordinates_Interior_Null [PASS] FromCoordinates_Null [PASS] FromPoints_Empty [PASS] FromPoints_Interior_Empty [PASS] FromPoints_Interior_Null [PASS] FromPoints_Null MonoTouchFixtures.MapKit.PolygonTest : 0.7619 ms MonoTouchFixtures.MapKit.PolygonViewTest [PASS] InitWithFrame MonoTouchFixtures.MapKit.PolygonViewTest : 0.1643 ms MonoTouchFixtures.MapKit.PolylineTest [PASS] From_PointEmpty [PASS] FromCoordinates_Empty [PASS] FromCoordinates_Null [PASS] FromPoints_Null MonoTouchFixtures.MapKit.PolylineTest : 0.3092 ms MonoTouchFixtures.MapKit.PolylineViewTest [PASS] InitWithFrame MonoTouchFixtures.MapKit.PolylineViewTest : 0.1434 ms MonoTouchFixtures.MapKit.ShapeTest MonoTouchFixtures.MapKit.ShapeTest : 0.001 ms MonoTouchFixtures.MapKit : 2105.3093 ms MonoTouchFixtures.MediaAccessibility MonoTouchFixtures.MediaAccessibility.AudibleMediaTest [PASS] PreferredCharacteristics MonoTouchFixtures.MediaAccessibility.AudibleMediaTest : 0.15 ms MonoTouchFixtures.MediaAccessibility.CaptionAppearanceTest [PASS] DidDisplayCaptions [PASS] GetDisplayType [PASS] IsCustomized [IGNORED] TestProfiles : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.MediaAccessibility.CaptionAppearanceTest.TestProfiles() MonoTouchFixtures.MediaAccessibility.CaptionAppearanceTest : 0.8164 ms MonoTouchFixtures.MediaAccessibility.ImageCaptioningTest [PASS] GetCaption [PASS] GetMetadataTagPath [PASS] SetCaption MonoTouchFixtures.MediaAccessibility.ImageCaptioningTest : 222.4743 ms MonoTouchFixtures.MediaAccessibility : 223.4507 ms MonoTouchFixtures.MediaPlayer MonoTouchFixtures.MediaPlayer.MediaItemArtworkTest [PASS] ImageWithSize MonoTouchFixtures.MediaPlayer.MediaItemArtworkTest : 1.1269 ms MonoTouchFixtures.MediaPlayer.MediaItemTest [IGNORED] DefaultValues : This test only runs on device. at MonoTouchFixtures.MediaPlayer.MediaItemTest.DefaultValues() MonoTouchFixtures.MediaPlayer.MediaItemTest : 0.4994 ms MonoTouchFixtures.MediaPlayer.MPRemoteCommandTest [PASS] RemoteTargetNull MonoTouchFixtures.MediaPlayer.MPRemoteCommandTest : 1.6257 ms MonoTouchFixtures.MediaPlayer.NowPlayingInfoCenterTest [PASS] NowPlaying MonoTouchFixtures.MediaPlayer.NowPlayingInfoCenterTest : 3.8522 ms MonoTouchFixtures.MediaPlayer.RemoteCommandCenterTest [PASS] Shared [PASS] Shared_8 [PASS] Shared_9 MonoTouchFixtures.MediaPlayer.RemoteCommandCenterTest : 0.7757 ms MonoTouchFixtures.MediaPlayer.SkipIntervalCommandTest [PASS] ManualBinding MonoTouchFixtures.MediaPlayer.SkipIntervalCommandTest : 0.3396 ms MonoTouchFixtures.MediaPlayer.VolumeViewTest [PASS] InitWithFrame MonoTouchFixtures.MediaPlayer.VolumeViewTest : 6.0679 ms MonoTouchFixtures.MediaPlayer : 14.3092 ms MonoTouchFixtures.MediaToolbox MonoTouchFixtures.MediaToolbox.AudioProcessingTapTest [PASS] Initialization MonoTouchFixtures.MediaToolbox.AudioProcessingTapTest : 0.5154 ms MonoTouchFixtures.MediaToolbox.FormatNamesTest [PASS] LocalizedNameForMediaSubType [PASS] LocalizedNameForMediaType MonoTouchFixtures.MediaToolbox.FormatNamesTest : 1.634 ms MonoTouchFixtures.MediaToolbox : 2.1701 ms MonoTouchFixtures.MessageUI MonoTouchFixtures.MessageUI.MailComposeViewControllerTest [PASS] MailComposeDelegate [PASS] TextShadowOffset_7443 MonoTouchFixtures.MessageUI.MailComposeViewControllerTest : 18.3723 ms MonoTouchFixtures.MessageUI : 18.3819 ms MonoTouchFixtures.Metal MonoTouchFixtures.Metal.ClearValueTest [PASS] Constructor MonoTouchFixtures.Metal.ClearValueTest : 0.1323 ms MonoTouchFixtures.Metal.DeviceTest [PASS] System MonoTouchFixtures.Metal.DeviceTest : 0.0924 ms MonoTouchFixtures.Metal.HeapDescriptorTest [PASS] CpuCacheModeTest [PASS] GetSetCpuCacheModeTest [PASS] GetSetSizeTest [PASS] GetSetStorageModeTest [PASS] HazardTrackingModeTest [PASS] Properties [PASS] ResourceOptionsTest [PASS] SizeTest [PASS] StorageModeTest [PASS] TypeTest MonoTouchFixtures.Metal.HeapDescriptorTest : 1.3199 ms MonoTouchFixtures.Metal.MTL4CommandBufferTests [IGNORED] UseResidencySets : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Metal.Helper.AssertMetal4Available(IMTLDevice& device) at MonoTouchFixtures.Metal.Helper.CreateMTL4CommandBuffer(IMTLDevice& device) at MonoTouchFixtures.Metal.MTL4CommandBufferTests.UseResidencySets() MonoTouchFixtures.Metal.MTL4CommandBufferTests : 0.3138 ms MonoTouchFixtures.Metal.MTL4CommandQueueTests [IGNORED] AddOrRemoveResidencySets : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Metal.Helper.AssertMetal4Available(IMTLDevice& device) at MonoTouchFixtures.Metal.Helper.CreateMTL4CommandQueue(IMTLDevice& device) at MonoTouchFixtures.Metal.MTL4CommandQueueTests.AddOrRemoveResidencySets() [IGNORED] Commit : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Metal.Helper.AssertMetal4Available(IMTLDevice& device) at MonoTouchFixtures.Metal.Helper.CreateMTL4CommandQueue(IMTLDevice& device) at MonoTouchFixtures.Metal.MTL4CommandQueueTests.Commit() [IGNORED] UpdateAndCopyBufferMappings : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Metal.Helper.AssertMetal4Available(IMTLDevice& device) at MonoTouchFixtures.Metal.Helper.CreateMTL4CommandQueue(IMTLDevice& device) at MonoTouchFixtures.Metal.MTL4CommandQueueTests.UpdateAndCopyBufferMappings() [IGNORED] UpdateAndCopyTextureMappings : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Metal.Helper.AssertMetal4Available(IMTLDevice& device) at MonoTouchFixtures.Metal.Helper.CreateMTL4CommandQueue(IMTLDevice& device) at MonoTouchFixtures.Metal.MTL4CommandQueueTests.UpdateAndCopyTextureMappings() MonoTouchFixtures.Metal.MTL4CommandQueueTests : 0.945 ms MonoTouchFixtures.Metal.MTL4CopySparseBufferMappingOperationTest [PASS] Constructor_Default_InitializesWithDefaultValues [PASS] DestinationOffset_SetAndGet_ReturnsCorrectValue [PASS] DestinationOffset_WithMaxValue_HandlesCorrectly [PASS] DestinationOffset_WithZeroValue_HandlesCorrectly [PASS] Properties_SetAllProperties_RetainsAllValues [PASS] SourceRange_SetAndGet_ReturnsCorrectValue [PASS] SourceRange_WithLargeValues_HandlesCorrectly [PASS] SourceRange_WithMaxValues_HandlesCorrectly [PASS] SourceRange_WithZeroLength_HandlesCorrectly [PASS] Struct_MultipleInstances_AreIndependent MonoTouchFixtures.Metal.MTL4CopySparseBufferMappingOperationTest : 0.4037 ms MonoTouchFixtures.Metal.MTL4CopySparseTextureMappingOperationTest [PASS] Constructor_Default_InitializesWithDefaultValues [PASS] DestinationLevel_SetAndGet_ReturnsCorrectValue [PASS] DestinationOrigin_SetAndGet_ReturnsCorrectValue [PASS] DestinationOrigin_WithZeroValues_HandlesCorrectly [PASS] DestinationSlice_SetAndGet_ReturnsCorrectValue [PASS] LevelAndSlice_WithMaxValues_HandlesCorrectly [PASS] LevelAndSlice_WithZeroValues_HandlesCorrectly [PASS] Properties_SetAllProperties_RetainsAllValues [PASS] SourceLevel_SetAndGet_ReturnsCorrectValue [PASS] SourceRegion_SetAndGet_ReturnsCorrectValue [PASS] SourceRegion_WithZeroSize_HandlesCorrectly [PASS] SourceSlice_SetAndGet_ReturnsCorrectValue [PASS] Struct_MultipleInstances_AreIndependent MonoTouchFixtures.Metal.MTL4CopySparseTextureMappingOperationTest : 0.8759 ms MonoTouchFixtures.Metal.MTL4RenderCommandEncoderTests [IGNORED] Viewports : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Metal.Helper.AssertMetal4Available(IMTLDevice& device) at MonoTouchFixtures.Metal.Helper.CreateMTL4CommandBuffer(IMTLDevice& device) at MonoTouchFixtures.Metal.MTL4RenderCommandEncoderTests.Viewports() MonoTouchFixtures.Metal.MTL4RenderCommandEncoderTests : 0.219 ms MonoTouchFixtures.Metal.MTL4UpdateSparseBufferMappingOperationTest [PASS] BufferRange_SetAndGet_ReturnsCorrectValue [PASS] BufferRange_WithMaxValues_HandlesCorrectly [PASS] BufferRange_WithZeroLength_HandlesCorrectly [PASS] Constructor_Default_InitializesWithDefaultValues [PASS] HeapOffset_SetAndGet_ReturnsCorrectValue [PASS] HeapOffset_WithMaxValue_HandlesCorrectly [PASS] HeapOffset_WithZeroValue_HandlesCorrectly [PASS] Mode_SetAndGet_ReturnsCorrectValue [PASS] Mode_WithAllValidValues_HandlesCorrectly [PASS] Properties_SetAllProperties_RetainsAllValues [PASS] Struct_MultipleInstances_AreIndependent MonoTouchFixtures.Metal.MTL4UpdateSparseBufferMappingOperationTest : 0.4745 ms MonoTouchFixtures.Metal.MTL4UpdateSparseTextureMappingOperationTest [PASS] Constructor_Default_InitializesWithDefaultValues [PASS] HeapOffset_SetAndGet_ReturnsCorrectValue [PASS] HeapOffset_WithMaxValue_HandlesCorrectly [PASS] HeapOffset_WithZeroValue_HandlesCorrectly [PASS] LevelAndSlice_WithMaxValues_HandlesCorrectly [PASS] LevelAndSlice_WithZeroValues_HandlesCorrectly [PASS] Mode_SetAndGet_ReturnsCorrectValue [PASS] Mode_WithAllValidValues_HandlesCorrectly [PASS] Properties_SetAllProperties_RetainsAllValues [PASS] Struct_MultipleInstances_AreIndependent [PASS] TextureLevel_SetAndGet_ReturnsCorrectValue [PASS] TextureRegion_SetAndGet_ReturnsCorrectValue [PASS] TextureRegion_WithLargeValues_HandlesCorrectly [PASS] TextureRegion_WithZeroSize_HandlesCorrectly [PASS] TextureSlice_SetAndGet_ReturnsCorrectValue MonoTouchFixtures.Metal.MTL4UpdateSparseTextureMappingOperationTest : 0.7383 ms MonoTouchFixtures.Metal.MTLArgumentDescriptorTest [PASS] GetSetAccessTest [PASS] GetSetArrayLengthTest [PASS] GetSetConstantBlockAlignmentTest [PASS] GetSetDataTypeTest [PASS] GetSetIndexTest [PASS] GetSetTextureTypeTest MonoTouchFixtures.Metal.MTLArgumentDescriptorTest : 0.8211 ms MonoTouchFixtures.Metal.MTLArgumentEncoderTest [PASS] SetBuffers MonoTouchFixtures.Metal.MTLArgumentEncoderTest : 1.0083 ms MonoTouchFixtures.Metal.MTLAttributeDescriptorTest [PASS] GetSetBufferIndexTest [PASS] GetSetFormatTest [PASS] GetSetOffsetTest MonoTouchFixtures.Metal.MTLAttributeDescriptorTest : 0.3234 ms MonoTouchFixtures.Metal.MTLAttributeTest [PASS] GetActiveTest [PASS] GetAttributeIndexTest [PASS] GetAttributeTypeTest [PASS] GetIsPatchControlPointDataTest [PASS] GetIsPatchDataTest [PASS] GetNameTest MonoTouchFixtures.Metal.MTLAttributeTest : 0.6436 ms MonoTouchFixtures.Metal.MTLBlitPassDescriptorTest [PASS] TestSampleBufferAttachments MonoTouchFixtures.Metal.MTLBlitPassDescriptorTest : 0.4315 ms MonoTouchFixtures.Metal.MTLBlitPassSampleBufferAttachmentDescriptorArrayTest [PASS] IndexerTest MonoTouchFixtures.Metal.MTLBlitPassSampleBufferAttachmentDescriptorArrayTest : 0.3318 ms MonoTouchFixtures.Metal.MTLBlitPassSampleBufferAttachmentDescriptorTest [PASS] EndOfEncoderSampleIndexTest [PASS] SampleBufferTest [PASS] StartOfEncoderSampleIndexTest MonoTouchFixtures.Metal.MTLBlitPassSampleBufferAttachmentDescriptorTest : 0.3113 ms MonoTouchFixtures.Metal.MTLBufferLayoutDescriptorTest [PASS] GetSetStepFunctionTest [PASS] GetSetStepRate [PASS] GetSetStrideTest MonoTouchFixtures.Metal.MTLBufferLayoutDescriptorTest : 0.2701 ms MonoTouchFixtures.Metal.MTLCommandBufferTests [PASS] UseResidencySets MonoTouchFixtures.Metal.MTLCommandBufferTests : 1.1026 ms MonoTouchFixtures.Metal.MTLCommandQueueTests [PASS] AddOrRemoveResidencySets MonoTouchFixtures.Metal.MTLCommandQueueTests : 0.6619 ms MonoTouchFixtures.Metal.MTLComputeCommandEncoderTest [PASS] SetBuffers MonoTouchFixtures.Metal.MTLComputeCommandEncoderTest : 0.7343 ms MonoTouchFixtures.Metal.MTLComputePassDescriptorTest [PASS] DispatchTypeTest [PASS] SampleBufferAttachments MonoTouchFixtures.Metal.MTLComputePassDescriptorTest : 0.4345 ms MonoTouchFixtures.Metal.MTLComputePassSampleBufferAttachmentDescriptorArrayTest [PASS] IndexerTest MonoTouchFixtures.Metal.MTLComputePassSampleBufferAttachmentDescriptorArrayTest : 0.3136 ms MonoTouchFixtures.Metal.MTLComputePassSampleBufferAttachmentDescriptorTest [PASS] EndOfEncoderSampleIndexTest [PASS] SampleBufferTest [PASS] StartOfEncoderSampleIndexTest MonoTouchFixtures.Metal.MTLComputePassSampleBufferAttachmentDescriptorTest : 0.283 ms MonoTouchFixtures.Metal.MTLCounterSampleBufferDescriptorTest [PASS] CounterSetTest [PASS] LabelTest [PASS] SampleCountTest [PASS] StorageModeTest MonoTouchFixtures.Metal.MTLCounterSampleBufferDescriptorTest : 0.3984 ms MonoTouchFixtures.Metal.MTLDeviceTests [PASS] GetAllDevicesTest [PASS] ReturnReleaseTest This device supports feature set: iOS_GPUFamily1_v1: True This device supports feature set: iOS_GPUFamily2_v1: True This device supports feature set: iOS_GPUFamily1_v2: True This device supports feature set: iOS_GPUFamily2_v2: True This device supports feature set: iOS_GPUFamily3_v1: True This device supports feature set: iOS_GPUFamily1_v3: True This device supports feature set: iOS_GPUFamily2_v3: True This device supports feature set: iOS_GPUFamily3_v2: True This device supports feature set: iOS_GPUFamily1_v4: True This device supports feature set: iOS_GPUFamily2_v4: True This device supports feature set: iOS_GPUFamily3_v3: True This device supports feature set: iOS_GPUFamily4_v1: True This device supports feature set: iOS_GPUFamily1_v5: True This device supports feature set: iOS_GPUFamily2_v5: True This device supports feature set: iOS_GPUFamily3_v4: True This device supports feature set: iOS_GPUFamily4_v2: True This device supports feature set: iOS_GPUFamily5_v1: True This device supports feature set: macOS_GPUFamily1_v1: True This device supports feature set: macOS_GPUFamily1_v2: True This device supports feature set: macOS_ReadWriteTextureTier2: False This device supports feature set: macOS_GPUFamily1_v3: True This device supports feature set: macOS_GPUFamily1_v4: True This device supports feature set: macOS_GPUFamily2_v1: True This device supports feature set: tvOS_GPUFamily1_v1: False This device supports feature set: tvOS_GPUFamily1_v2: False This device supports feature set: tvOS_GPUFamily1_v3: False This device supports feature set: tvOS_GPUFamily2_v1: False This device supports feature set: tvOS_GPUFamily1_v4: False This device supports feature set: tvOS_GPUFamily2_v2: False This device supports Gpu family: Apple1: True This device supports Gpu family: Apple2: True This device supports Gpu family: Apple3: True This device supports Gpu family: Apple4: True This device supports Gpu family: Apple5: True This device supports Gpu family: Apple6: True This device supports Gpu family: Apple7: True This device supports Gpu family: Apple8: True This device supports Gpu family: Apple9: False This device supports Gpu family: Apple10: False This device supports Gpu family: Mac1: True This device supports Gpu family: Mac2: True This device supports Gpu family: Common1: True This device supports Gpu family: Common2: True This device supports Gpu family: Common3: True This device supports Gpu family: iOSMac1: True This device supports Gpu family: iOSMac2: True This device supports Gpu family: Metal3: True This device supports Gpu family: Metal4: False [PASS] SystemDefault MonoTouchFixtures.Metal.MTLDeviceTests : 11.0168 ms MonoTouchFixtures.Metal.MTLFunctionConstantTest [PASS] GetIndexTest [PASS] GetIsRequiredTest [PASS] GetNameTest [PASS] GetTypeTest MonoTouchFixtures.Metal.MTLFunctionConstantTest : 0.3035 ms MonoTouchFixtures.Metal.MTLIndirectCommandBufferDescriptorTest [PASS] GetSetCommandTypesTest [PASS] GetSetInheritBuffersTest [PASS] GetSetMaxFragmentBufferBindCountTest [PASS] GetSetMaxVertexBufferBindCountTest MonoTouchFixtures.Metal.MTLIndirectCommandBufferDescriptorTest : 0.285 ms MonoTouchFixtures.Metal.MTLIntersectionFunctionTableDescriptorTest [PASS] FunctionCountTest MonoTouchFixtures.Metal.MTLIntersectionFunctionTableDescriptorTest : 0.2559 ms MonoTouchFixtures.Metal.MTLIntersectionFunctionTableTests [INCONCLUSIVE] SetBuffersTest : Could not create pipeline RasterizationEnabled is false but the vertex shader's return type is not void at MonoTouchFixtures.Metal.MTLIntersectionFunctionTableTests.SetUp() MonoTouchFixtures.Metal.MTLIntersectionFunctionTableTests : 32.3868 ms MonoTouchFixtures.Metal.MTLIOCompressionContextTest [PASS] CreateAndFlushTest [PASS] DefaultChunkSize MonoTouchFixtures.Metal.MTLIOCompressionContextTest : 3.123 ms MonoTouchFixtures.Metal.MTLLinkedFunctionsTest [PASS] BinaryFunctions [PASS] FunctionsTest [PASS] Groupstest MonoTouchFixtures.Metal.MTLLinkedFunctionsTest : 0.5442 ms MonoTouchFixtures.Metal.MTLPipelineBufferDescriptorTest [PASS] GetSetMutabilityTest MonoTouchFixtures.Metal.MTLPipelineBufferDescriptorTest : 0.1287 ms MonoTouchFixtures.Metal.MTLPointerTypeTests [PASS] GetAccessTest [PASS] GetAlignmentTest [PASS] GetDataSizeTest [PASS] GetElementIsArgumentBufferTest [PASS] GetElementTypeTest MonoTouchFixtures.Metal.MTLPointerTypeTests : 0.4454 ms MonoTouchFixtures.Metal.MTLRasterizationRateLayerDescriptorTest [PASS] Create_1 [PASS] Create_2 [PASS] Ctor [PASS] Horizontal [PASS] MaxSampleCount [PASS] SampleCount [PASS] Vertical MonoTouchFixtures.Metal.MTLRasterizationRateLayerDescriptorTest : 2.6317 ms MonoTouchFixtures.Metal.MTLRasterizationRateMapDescriptorTest [PASS] GetLayerTest [PASS] LabelTest [PASS] LayerCount [PASS] LayersTest [PASS] ScreenSizeTest [PASS] SetLayerTest MonoTouchFixtures.Metal.MTLRasterizationRateMapDescriptorTest : 0.7376 ms MonoTouchFixtures.Metal.MTLRenderPassSampleBufferAttachmentDescriptorArrayTest [PASS] IndexerTest MonoTouchFixtures.Metal.MTLRenderPassSampleBufferAttachmentDescriptorArrayTest : 0.3455 ms MonoTouchFixtures.Metal.MTLRenderPassSampleBufferAttachmentDescriptorTest [PASS] EndOfFragmentSampleIndexTest [PASS] EndOfVertexSampleIndexTest [PASS] SampleBufferTest [PASS] StartOfFragmentSampleIndexTest [PASS] StartOfVertexSampleIndexTest MonoTouchFixtures.Metal.MTLRenderPassSampleBufferAttachmentDescriptorTest : 0.5745 ms MonoTouchFixtures.Metal.MTLRenderPipelineDescriptorTest [PASS] AlphaToCoverageEnabledTest [PASS] AlphaToOneEnabledTest [PASS] ColorAttachments [PASS] DepthAttachmentPixelFormatTest [PASS] FragmentFunctionTest [PASS] InputPrimitiveTopologyTest [PASS] IsTessellationFactorScaleEnabledTest [PASS] LabelTest [PASS] MaxTessellationFactorTest [PASS] RasterizationEnabledTest [PASS] ResetTest [PASS] SampleCountTest [PASS] StencilAttachmentPixelFormatTest [PASS] TessellationControlPointIndexTypeTest [PASS] TessellationFactorFormatTest [PASS] TessellationPartitionModeTest [PASS] VertexDescriptorTest [PASS] VertexFunctionTest MonoTouchFixtures.Metal.MTLRenderPipelineDescriptorTest : 1.7462 ms MonoTouchFixtures.Metal.MTLResidencySetTests [PASS] AddOrRemoveAllocations MonoTouchFixtures.Metal.MTLResidencySetTests : 0.3052 ms MonoTouchFixtures.Metal.MTLResourceStatePassDescriptorTest [PASS] SampleBufferAttachments MonoTouchFixtures.Metal.MTLResourceStatePassDescriptorTest : 0.3962 ms MonoTouchFixtures.Metal.MTLResourceStatePassSampleBufferAttachmentDescriptorArrayTest [PASS] IndexerTest MonoTouchFixtures.Metal.MTLResourceStatePassSampleBufferAttachmentDescriptorArrayTest : 0.3218 ms MonoTouchFixtures.Metal.MTLResourceStatePassSampleBufferAttachmentDescriptorTest [PASS] EndOfEncoderSampleIndexTest [PASS] SampleBufferTest [PASS] StartOfEncoderSampleIndexTest MonoTouchFixtures.Metal.MTLResourceStatePassSampleBufferAttachmentDescriptorTest : 0.2772 ms MonoTouchFixtures.Metal.MTLSharedEventListenerTest [IGNORED] GetSetCommandTypesTest : This test only runs on device. at MonoTouchFixtures.Metal.MTLSharedEventListenerTest.SetUp() MonoTouchFixtures.Metal.MTLSharedEventListenerTest : 0.167 ms MonoTouchFixtures.Metal.MTLStageInputOutputDescriptorTest [PASS] GetAttributesTest [PASS] GetLayoutsTest [PASS] GetSetIndexBufferTest [PASS] GetSetIndexType MonoTouchFixtures.Metal.MTLStageInputOutputDescriptorTest : 0.8098 ms MonoTouchFixtures.Metal.MTLStageInRegionIndirectArgumentsTest [PASS] SizeOfMTLStageInRegionIndirectArgumentsTest MonoTouchFixtures.Metal.MTLStageInRegionIndirectArgumentsTest : 0.0523 ms MonoTouchFixtures.Metal.MTLTensorExtentsTest [IGNORED] Constructor : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Metal.MTLTensorExtentsTest.Constructor() MonoTouchFixtures.Metal.MTLTensorExtentsTest : 0.176 ms MonoTouchFixtures.Metal.MTLTextureReferenceTypeTests [PASS] GetAccessTest [PASS] GetIsDepthTextureTest [PASS] GetTextureDataType [PASS] GetTextureType MonoTouchFixtures.Metal.MTLTextureReferenceTypeTests : 0.3163 ms MonoTouchFixtures.Metal.MTLTileRenderPipelineColorAttachmentDescriptorTests [PASS] GetSetPixelFormat MonoTouchFixtures.Metal.MTLTileRenderPipelineColorAttachmentDescriptorTests : 0.1365 ms MonoTouchFixtures.Metal.MTLTileRenderPipelineDescriptorTest [PASS] BinaryArchivesTest MonoTouchFixtures.Metal.MTLTileRenderPipelineDescriptorTest : 0.1738 ms MonoTouchFixtures.Metal.MTLTileRenderPipelineDescriptorTests [PASS] ColorAttachmentsTest [PASS] GetSetLabelTest [PASS] GetSetMaxTotalThreadsPerThreadgroupTest [PASS] GetSetRasterSampleCount [PASS] GetSetThreadgroupSizeMatchesTileSize [PASS] GetTileBuffers MonoTouchFixtures.Metal.MTLTileRenderPipelineDescriptorTests : 0.561 ms MonoTouchFixtures.Metal.MTLVisibleFunctionTableDescriptorTest [PASS] FunctionCountTest MonoTouchFixtures.Metal.MTLVisibleFunctionTableDescriptorTest : 0.2482 ms MonoTouchFixtures.Metal.StructTest [PASS] MTLQuadTessellationFactorsHalfStructSize [PASS] MTLTriangleTessellationFactorsHalfStructSize MonoTouchFixtures.Metal.StructTest : 0.1081 ms MonoTouchFixtures.Metal : 72.4995 ms MonoTouchFixtures.MetalPerformanceShaders MonoTouchFixtures.MetalPerformanceShaders.ImageScaleTest [PASS] ScaleTransform MonoTouchFixtures.MetalPerformanceShaders.ImageScaleTest : 0.3173 ms MonoTouchFixtures.MetalPerformanceShaders.KernelTest [IGNORED] MPSImageLaplacianPyramidCtorArrTest : This test only runs on device. at MonoTouchFixtures.MetalPerformanceShaders.KernelTest.MPSImageLaplacianPyramidCtorArrTest() [IGNORED] MPSKernelCopyTest : This test only runs on device. at MonoTouchFixtures.MetalPerformanceShaders.KernelTest.MPSKernelCopyTest() [IGNORED] MPSRnnImageInferenceLayerCopyTest : This test only runs on device. at MonoTouchFixtures.MetalPerformanceShaders.KernelTest.MPSRnnImageInferenceLayerCopyTest() [IGNORED] MPSRnnMatrixInferenceLayerTest : This test only runs on device. at MonoTouchFixtures.MetalPerformanceShaders.KernelTest.MPSRnnMatrixInferenceLayerTest() [PASS] RectNoClip MonoTouchFixtures.MetalPerformanceShaders.KernelTest : 0.4533 ms MonoTouchFixtures.MetalPerformanceShaders.MPSAccelerationStructureTests [IGNORED] MPSAxisAlignedBoundingBoxTest : OneTimeSetUp: This test only runs on device. MonoTouchFixtures.MetalPerformanceShaders.MPSAccelerationStructureTests : 0.1013 ms MonoTouchFixtures.MetalPerformanceShaders.MPSImageBatchTests [IGNORED] IncrementReadCountTest : OneTimeSetUp: This test only runs on device. [IGNORED] MPSImageBatchResourceSizeTest : OneTimeSetUp: This test only runs on device. MonoTouchFixtures.MetalPerformanceShaders.MPSImageBatchTests : 0.1383 ms MonoTouchFixtures.MetalPerformanceShaders.MPSImageCannyTest [PASS] Create MonoTouchFixtures.MetalPerformanceShaders.MPSImageCannyTest : 3.9306 ms MonoTouchFixtures.MetalPerformanceShaders.MPSImageHistogramEqualizationTest [PASS] Constructors MonoTouchFixtures.MetalPerformanceShaders.MPSImageHistogramEqualizationTest : 0.3357 ms MonoTouchFixtures.MetalPerformanceShaders.MPSImageHistogramSpecificationTest [PASS] Constructors MonoTouchFixtures.MetalPerformanceShaders.MPSImageHistogramSpecificationTest : 0.1849 ms MonoTouchFixtures.MetalPerformanceShaders.MPSImageHistogramTest [PASS] Constructors MonoTouchFixtures.MetalPerformanceShaders.MPSImageHistogramTest : 0.3079 ms MonoTouchFixtures.MetalPerformanceShaders.MPSImageNormalizedHistogramTests [IGNORED] Constructors : OneTimeSetUp: This test only runs on device. MonoTouchFixtures.MetalPerformanceShaders.MPSImageNormalizedHistogramTests : 0.0763 ms MonoTouchFixtures.MetalPerformanceShaders.MPSNDArrayDescriptorTest [PASS] DimensionOrderTest [PASS] PermuteWithDimensionOrderTest MonoTouchFixtures.MetalPerformanceShaders.MPSNDArrayDescriptorTest : 0.4547 ms MonoTouchFixtures.MetalPerformanceShaders.MPSNDArrayIdentityTest [PASS] ReshapeA [PASS] ReshapeB MonoTouchFixtures.MetalPerformanceShaders.MPSNDArrayIdentityTest : 8.4792 ms MonoTouchFixtures.MetalPerformanceShaders.MPSNDArrayOffsetsTest [PASS] Dimensions MonoTouchFixtures.MetalPerformanceShaders.MPSNDArrayOffsetsTest : 0.5758 ms MonoTouchFixtures.MetalPerformanceShaders.MPSNDArraySizesTest [PASS] Dimensions MonoTouchFixtures.MetalPerformanceShaders.MPSNDArraySizesTest : 0.8535 ms MonoTouchFixtures.MetalPerformanceShaders.MPSNDArrayTest [PASS] Create MonoTouchFixtures.MetalPerformanceShaders.MPSNDArrayTest : 0.1501 ms MonoTouchFixtures.MetalPerformanceShaders.MPSNNGraphTest [PASS] Create [PASS] Ctor MonoTouchFixtures.MetalPerformanceShaders.MPSNNGraphTest : 0.5967 ms MonoTouchFixtures.MetalPerformanceShaders.MPSNNOptimizerStochasticGradientDescentTest [PASS] Create MonoTouchFixtures.MetalPerformanceShaders.MPSNNOptimizerStochasticGradientDescentTest : 2.959 ms MonoTouchFixtures.MetalPerformanceShaders.MPSStateBatchTests [IGNORED] IncrementReadCountTest : OneTimeSetUp: This test only runs on device. [IGNORED] MPSImageBatchResourceSizeTest : OneTimeSetUp: This test only runs on device. MonoTouchFixtures.MetalPerformanceShaders.MPSStateBatchTests : 0.1542 ms MonoTouchFixtures.MetalPerformanceShaders.MPSStateResourceListTests [IGNORED] CreateTest : OneTimeSetUp: This test only runs on device. [IGNORED] MTLTextureDescriptorCreateTest : OneTimeSetUp: This test only runs on device. [IGNORED] SizesCreateTest : OneTimeSetUp: This test only runs on device. MonoTouchFixtures.MetalPerformanceShaders.MPSStateResourceListTests : 0.0746 ms MonoTouchFixtures.MetalPerformanceShaders : 20.2038 ms MonoTouchFixtures.MetalPerformanceShadersGraph MonoTouchFixtures.MetalPerformanceShadersGraph.MnistTester [IGNORED] Run : This test frequently fails, so just ignore it for now. MonoTouchFixtures.MetalPerformanceShadersGraph.MnistTester : 0.077 ms MonoTouchFixtures.MetalPerformanceShadersGraph : 0.08 ms MonoTouchFixtures.MetricKit MonoTouchFixtures.MetricKit.MetricManagerTest [PASS] MakeLogHandle MonoTouchFixtures.MetricKit.MetricManagerTest : 0.125 ms MonoTouchFixtures.MetricKit : 0.1275 ms MonoTouchFixtures.MLCompute MonoTouchFixtures.MLCompute.MLEnumsTest [PASS] GetDebugDescription MonoTouchFixtures.MLCompute.MLEnumsTest : 0.275 ms MonoTouchFixtures.MLCompute : 0.2777 ms MonoTouchFixtures.MobileCoreServices MonoTouchFixtures.MobileCoreServices.UTTypeTest [PASS] CreatePreferredIdentifier [PASS] Equals [PASS] GetDeclaration [PASS] GetDeclaringBundleURL [PASS] GetPreferredTag [PASS] NSStringConstants MonoTouchFixtures.MobileCoreServices.UTTypeTest : 6.0057 ms MonoTouchFixtures.MobileCoreServices : 6.0086 ms MonoTouchFixtures.ModelIO MonoTouchFixtures.ModelIO.A_MDLAnimatedValueTypesTests [PASS] MDLAnimatedMatrix4x4Test [PASS] MDLAnimatedQuaternionArrayTest [PASS] MDLAnimatedQuaternionTest [PASS] MDLAnimatedScalarArrayTest [PASS] MDLAnimatedScalarTest [PASS] MDLAnimatedVector2Test [PASS] MDLAnimatedVector3ArrayTest [PASS] MDLAnimatedVector3Test [PASS] MDLAnimatedVector4Test [PASS] MDLMatrix4x4ArrayTest [PASS] OpenTKSizeOfTests MonoTouchFixtures.ModelIO.A_MDLAnimatedValueTypesTests : 37.6723 ms MonoTouchFixtures.ModelIO.MDCameraTest MonoTouchFixtures.ModelIO.MDCameraTest : 0.001 ms MonoTouchFixtures.ModelIO.MDLAssetTest [PASS] BoundingBoxTest MonoTouchFixtures.ModelIO.MDLAssetTest : 0.2462 ms MonoTouchFixtures.ModelIO.MDLLightTest [PASS] IrradianceAtPointTest MonoTouchFixtures.ModelIO.MDLLightTest : 0.2079 ms MonoTouchFixtures.ModelIO.MDLMaterialPropertyTest [PASS] Copy [PASS] Ctors MonoTouchFixtures.ModelIO.MDLMaterialPropertyTest : 1.4398 ms MonoTouchFixtures.ModelIO.MDLMeshTest [PASS] CreateBoxWithDimensonTest [PASS] CreateBoxWithExtentTest [PASS] CreateCapsuleTest [PASS] CreateConeTest [PASS] CreateCylinderTest [PASS] CreateCylindroidTest [PASS] CreateEllipsoidTest [PASS] CreateEllipticalConeTest [PASS] CreateHemisphereTest [PASS] CreateIcosahedronTest [PASS] CreatePaneTest [PASS] CreatePlaneTest [PASS] CreateSphereTest MonoTouchFixtures.ModelIO.MDLMeshTest : 4.9258 ms MonoTouchFixtures.ModelIO.MDLNoiseTextureTest [PASS] Ctor MonoTouchFixtures.ModelIO.MDLNoiseTextureTest : 0.1601 ms MonoTouchFixtures.ModelIO.MDLObjectTest [PASS] GetBoundingBox [PASS] ProtocolTest MonoTouchFixtures.ModelIO.MDLObjectTest : 1.8702 ms MonoTouchFixtures.ModelIO.MDLStereoscopicCameraTest [PASS] Properties MonoTouchFixtures.ModelIO.MDLStereoscopicCameraTest : 0.7131 ms MonoTouchFixtures.ModelIO.MDLTextureTest [PASS] CreateIrradianceTextureCubeTest_a [PASS] CreateIrradianceTextureCubeTest_b [PASS] Ctor [PASS] DimensionsTest MonoTouchFixtures.ModelIO.MDLTextureTest : 0.2998 ms MonoTouchFixtures.ModelIO.MDLTransformComponentTest [PASS] CreateGlobalTransformTest [PASS] LocalTransformTest [PASS] MatrixTest MonoTouchFixtures.ModelIO.MDLTransformComponentTest : 1.3863 ms MonoTouchFixtures.ModelIO.MDLTransformTest [PASS] Ctors [PASS] GetRotationMatrixTest [PASS] RotationAtTimeTest [PASS] ScaleAtTimeTest [PASS] TranslationAtTimeTest MonoTouchFixtures.ModelIO.MDLTransformTest : 0.875 ms MonoTouchFixtures.ModelIO.MDLVertexAttributeTest [PASS] Ctors [PASS] Properties MonoTouchFixtures.ModelIO.MDLVertexAttributeTest : 0.3682 ms MonoTouchFixtures.ModelIO.MDLVoxelArrayTest [PASS] BoundingBoxTest MonoTouchFixtures.ModelIO.MDLVoxelArrayTest : 0.3031 ms MonoTouchFixtures.ModelIO : 50.597 ms MonoTouchFixtures.MonoRuntimeTests [PASS] Bug18632 [PASS] Bug26989 MonoTouchFixtures.MonoRuntimeTests : 3.0461 ms MonoTouchFixtures.MultipeerConnectivity MonoTouchFixtures.MultipeerConnectivity.PeerIDTest [PASS] Defaults [PASS] LocalPeer MonoTouchFixtures.MultipeerConnectivity.PeerIDTest : 0.1989 ms MonoTouchFixtures.MultipeerConnectivity.SessionTest [PASS] Ctor_Identity [PASS] Ctor_Identity_Certificates [PASS] Ctor_OptionalIdentity [PASS] CtorPeer MonoTouchFixtures.MultipeerConnectivity.SessionTest : 211.3932 ms MonoTouchFixtures.MultipeerConnectivity : 211.6053 ms MonoTouchFixtures.NaturalLanguage MonoTouchFixtures.NaturalLanguage.EmbeddingTest [PASS] Vector [PASS] Write MonoTouchFixtures.NaturalLanguage.EmbeddingTest : 679.379 ms MonoTouchFixtures.NaturalLanguage.GazetteerTest [PASS] Dictionary MonoTouchFixtures.NaturalLanguage.GazetteerTest : 1.8454 ms MonoTouchFixtures.NaturalLanguage.NLLanguageRecognizerTest [PASS] GetDominantLanguageTest [PASS] HandelNumbers [PASS] Process MonoTouchFixtures.NaturalLanguage.NLLanguageRecognizerTest : 7.0452 ms MonoTouchFixtures.NaturalLanguage.NLTaggerTest [PASS] GetModels [PASS] GetNativeTagHypothesesNullSchemeTest [PASS] GetTag [PASS] GetTagHypotheses [PASS] GetTagHypotheses_Range [PASS] GetTags MonoTouchFixtures.NaturalLanguage.NLTaggerTest : 157.9393 ms MonoTouchFixtures.NaturalLanguage : 846.2284 ms MonoTouchFixtures.NearbyInteraction MonoTouchFixtures.NearbyInteraction.NIAlgorithmConvergenceStatusReasonValuesTest [PASS] GetConvergenceStatusReasonTest MonoTouchFixtures.NearbyInteraction.NIAlgorithmConvergenceStatusReasonValuesTest : 1.8512 ms MonoTouchFixtures.NearbyInteraction.NINearbyObjectTest [PASS] DirectionNotAvailable [PASS] WorldTransformNotAvailable MonoTouchFixtures.NearbyInteraction.NINearbyObjectTest : 1.0769 ms MonoTouchFixtures.NearbyInteraction : 2.9368 ms MonoTouchFixtures.Network MonoTouchFixtures.Network.NSUrlTest [PASS] ImplicitConversion MonoTouchFixtures.Network.NSUrlTest : 0.0453 ms MonoTouchFixtures.Network.NWBrowserDescriptorTest [PASS] TestApplicationServiceConstructor [PASS] TestBonjourDomainProperty [PASS] TestBonjourTypeProperty [PASS] TestCreateNullDomain [PASS] TestIncludeTxtRecordProperty MonoTouchFixtures.Network.NWBrowserDescriptorTest : 0.4671 ms MonoTouchFixtures.Network.NWBrowserTest [PASS] TestConstructorNullParameters [PASS] TestDispatchQueuPropertyNull [PASS] TestStart [PASS] TestStartNoQ [PASS] TestStateChangesHandler Not ignoring test, because not running in CI: This test may pop up a dialog asking for access to the local network, which will cause the test to fail. 2025-11-20 19:46:16.4903100 Starting async... 2025-11-20 19:46:16.4974990 Starting browser... 2025-11-20 19:46:17.4632990 listener.SetStateChangedHandler (Ready, (ErrorCode = , ErrorDomain = , CFError: , CFError.FailureReason: )) 2025-11-20 19:46:17.4633520 listener.SetStateChangedHandler (Cancelled, (ErrorCode = , ErrorDomain = , CFError: , CFError.FailureReason: )) 2025-11-20 19:46:17.5604070 Async done... 2025-11-20 19:46:17.5606120 about to cancel... 2025-11-20 19:46:17.5606850 cancelled... MonoTouchFixtures.Network.NWBrowserTest : 1183.3405 ms MonoTouchFixtures.Network.NWConnectionGroupTest [PASS] ExtractConnectionTest [PASS] GetProtocolMetadataContextDefinitionTest [PASS] GetProtocolMetadataContextTest [PASS] GroupDescriptorTest [PASS] ParametersTest [PASS] SetNewConnectionHandlerTest [PASS] SetQueueTest [PASS] TryReinsertExtractedConnectionTest MonoTouchFixtures.Network.NWConnectionGroupTest : 2.1799 ms MonoTouchFixtures.Network.NWConnectionTest [PASS] TestCancel [PASS] TestEndpointProperty [PASS] TestForceCancel [PASS] TestParametersProperty [PASS] TestSetQPropertyNull MonoTouchFixtures.Network.NWConnectionTest : 6023.8859 ms MonoTouchFixtures.Network.NWEndpointTests [PASS] BonjourServiceDomainTest [PASS] BonjourServiceNameTest [PASS] BonjourServiceTypeTest [PASS] HostNameTest [PASS] PortTest [PASS] SignatureTest [PASS] TxtRecordTest [PASS] TypeTest [PASS] UrlTest MonoTouchFixtures.Network.NWEndpointTests : 1.6811 ms MonoTouchFixtures.Network.NWEstablishmentReportTest [PASS] EnumerateResolutionReportsTest [PASS] TestConnectionSetupTime [PASS] TestDuration [PASS] TestEnumerateResolutions [PASS] TestPreviousAttemptCount [PASS] TestProxyConfigured Not ignoring test, because not running in CI: CI bots might have proxies setup and will mean that the test will fail. [PASS] TestProxyEnpoint Not ignoring test, because not running in CI: CI bots might have proxies setup and will mean that the test will fail. [PASS] TestUsedProxy Not ignoring test, because not running in CI: CI bots might have proxies setup and will mean that the test will fail. MonoTouchFixtures.Network.NWEstablishmentReportTest : 3.1118 ms MonoTouchFixtures.Network.NWFramerMessageTest [PASS] TestGetData [PASS] TestGetDataMissingKey [PASS] TestGetObject [PASS] TestGetObjectMissingKey MonoTouchFixtures.Network.NWFramerMessageTest : 1.2631 ms MonoTouchFixtures.Network.NWIPProtocolMetadataTest [PASS] TestEcnFlagProperty [PASS] TestMetadataType [PASS] TestReceiveTimeProperty [PASS] TestServiceClassProperty MonoTouchFixtures.Network.NWIPProtocolMetadataTest : 1.156 ms MonoTouchFixtures.Network.NWListenerTest [PASS] SetNewConnectionGroupHandlerTest [PASS] TestConnectionLimit MonoTouchFixtures.Network.NWListenerTest : 1.391 ms MonoTouchFixtures.Network.NWMulticastGroupTest [PASS] AddEndpointTest [PASS] DisabledUnicastTrafficTest [PASS] SetSpecificSourceTest MonoTouchFixtures.Network.NWMulticastGroupTest : 1.0887 ms MonoTouchFixtures.Network.NWParametersTest [PASS] AttributionPropertyTest [PASS] CreateApplicationServiceTest [PASS] CreateSecureTcpTest [PASS] CreateSecureTcpTestDoNotSetUpProtocol [PASS] CreateSecureTcpTestDoNotSetUpTls [PASS] CreateSecureUpdTest [PASS] CreateSecureUpdTestDoNotSetUpProtocol [PASS] CreateSecureUpdTestDoNotSetUpTls [PASS] ExpiredDnsBehaviorPropertyTest [PASS] FastOpenEnabledPropertyTest [PASS] IncludePeerToPeerPropertyTest [IGNORED] LocalEndpointPropertyTest : nw_parameters_copy_local_endpoint always return null. Rdar filled 44095278. at MonoTouchFixtures.Network.NWParametersTest.LocalEndpointPropertyTest() [PASS] LocalOnlyPropertyTest [IGNORED] MiscPropertiesTest : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Network.NWParametersTest.MiscPropertiesTest() [PASS] MultiPathServicePropertyTest [PASS] PreferNoProxyPropertyTest [PASS] ProhibitInterfaceTest [PASS] ProhibitInterfaceTypeTest [PASS] ProtocolStackPropertyTest [PASS] RequiredInterfacePropertyTest [PASS] RequiredInterfaceTypePropertyTest [PASS] ReuseLocalAddressPropertyTest [PASS] ServiceClassPropertyTest [IGNORED] SetPrivacyContextTest : Crashes everywhere. Feedback filed: https://github.com/xamarin/maccore/issues/2675 [PASS] TestProhibitConstrained MonoTouchFixtures.Network.NWParametersTest : 7.4781 ms MonoTouchFixtures.Network.NWPathTest [PASS] EnumerateGatewayNullCallbackTest [PASS] EnumerateGatewayTest [PASS] GetUnsatisfiedReason [PASS] HasDnsPropertyTest [PASS] HasIPV4PropertyTest [IGNORED] HasIPV6PropertyTest : We cannot test the use of IPV6 since it is different per machine configuraton and makes the test flaky. at MonoTouchFixtures.Network.NWPathTest.HasIPV6PropertyTest() [PASS] IsExpensivePropertyTest [IGNORED] IsUltraConstrained : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Network.NWPathTest.IsUltraConstrained() [PASS] IterateInterfacesTest [IGNORED] LinkQuality : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Network.NWPathTest.LinkQuality() [PASS] StatusPropertyTest [PASS] UsesInterfaceTypeTest MonoTouchFixtures.Network.NWPathTest : 6.5695 ms MonoTouchFixtures.Network.NWProtocolDefinitionTest [PASS] IPDefinitionTest [PASS] TcpDefinitionTest [PASS] TlsDefinitionTest [PASS] UdpDefinitionTest [PASS] WebSocketDefinitionTest MonoTouchFixtures.Network.NWProtocolDefinitionTest : 0.4457 ms MonoTouchFixtures.Network.NWProtocolIPOptionsTest [PASS] DisableMulticastLoopbackTest [PASS] SetCaculateReceiveTimeTest [PASS] SetDisableFragmentation [PASS] SetHopLimitTest [PASS] SetIPLocalAddressPreference [PASS] SetIPVersionTest [PASS] SetUseMinimumMtu MonoTouchFixtures.Network.NWProtocolIPOptionsTest : 56.3557 ms MonoTouchFixtures.Network.NWProtocolMetadataTest [PASS] IP [PASS] Quic [PASS] Udp MonoTouchFixtures.Network.NWProtocolMetadataTest : 1.093 ms MonoTouchFixtures.Network.NWProtocolOptionsTest [PASS] CreateTcpTest [PASS] CreateTlsTest [PASS] CreateUdpTest [PASS] SetIPLocalAddressPreference MonoTouchFixtures.Network.NWProtocolOptionsTest : 1.032 ms MonoTouchFixtures.Network.NWProtocolStackTest [PASS] ClearApplicationProtocolsTest [PASS] PrependApplicationProtocolNullOptionsTest MonoTouchFixtures.Network.NWProtocolStackTest : 9.8393 ms MonoTouchFixtures.Network.NWProtocolTcpOptionsTest [PASS] ConnectionTimeoutTest [PASS] DisableAckStretchingTest [PASS] DisableEcnTest [PASS] EnableFastOpenTest [PASS] EnableKeepAliveTest [PASS] ForceMultipathVersionTest [PASS] KeepAliveCountTest [PASS] KeepAliveIdleTimeTest [PASS] MaximumSegmentSizeTest [PASS] NoDelayTest [PASS] NoOptionsTest [PASS] NoPushTest [PASS] PersistTimeoutTest [PASS] RetransmitConnectionDropTimeTest [PASS] RetransmitFinDropTest MonoTouchFixtures.Network.NWProtocolTcpOptionsTest : 2.4728 ms MonoTouchFixtures.Network.NWProtocolTlsOptionsTest [PASS] ProtocolOptionsTest MonoTouchFixtures.Network.NWProtocolTlsOptionsTest : 0.1862 ms MonoTouchFixtures.Network.NWProtocolUdpOptionsTest [PASS] PreferNoChecksumTest MonoTouchFixtures.Network.NWProtocolUdpOptionsTest : 0.2396 ms MonoTouchFixtures.Network.NWProxyConfigTests [PASS] AddExcludedDomainTest [PASS] AddMatchDomainTest [PASS] ClearExcludedDomainsTest [PASS] ClearMatchDomainsTest [PASS] CreateHttpConnectTest [PASS] CreateObliviousHttpTest [PASS] CreateRelayTest [PASS] CreateSocksV5Test [PASS] DefaultSessionConfigurationProxyConfigurationsTests [PASS] EnumerateExcludedDomainsTest [PASS] EnumerateMatchDomainsTest [PASS] FailoverAllowedTest [PASS] SetUsernameAndPasswordTest MonoTouchFixtures.Network.NWProxyConfigTests : 3.2702 ms MonoTouchFixtures.Network.NWRelayHopTests [PASS] AddAdditionalHttpHeaderFieldTest MonoTouchFixtures.Network.NWRelayHopTests : 0.3049 ms MonoTouchFixtures.Network.NWResolutionReportTest [IGNORED] EndpointCountTest : OneTimeSetUp: This test only runs on device. [IGNORED] MillisecondsTest : OneTimeSetUp: This test only runs on device. [IGNORED] PreferredEndpointTest : OneTimeSetUp: This test only runs on device. [IGNORED] ProtocolTest : OneTimeSetUp: This test only runs on device. [IGNORED] SourceTest : OneTimeSetUp: This test only runs on device. [IGNORED] SuccessfulEndpointTest : OneTimeSetUp: This test only runs on device. MonoTouchFixtures.Network.NWResolutionReportTest : 0.3144 ms MonoTouchFixtures.Network.NWResolverConfigTest [PASS] AddServerAddressTest [PASS] HttpConstructorTest MonoTouchFixtures.Network.NWResolverConfigTest : 0.4646 ms MonoTouchFixtures.Network.NWTxtRecordTest [PASS] TestAddByteValue [PASS] TestAddNoValue [PASS] TestAddNullStringValue [PASS] TestAddStringValue [PASS] TestApply [PASS] TestFromBytes [PASS] TestGetRaw [PASS] TestGetValueMissing [PASS] TestGetValuePresent [PASS] TestIsDictionary [PASS] TestKeyCount [PASS] TestMissingKey [PASS] TestNotNullEquals [PASS] TestPresentKey [PASS] TestRemoveMissingKey [PASS] TestRemovePresentKey MonoTouchFixtures.Network.NWTxtRecordTest : 1.5235 ms MonoTouchFixtures.Network.NWWebSocketMetadataTest [PASS] TestConstructor [PASS] TestPongHandlerNullCallaback [PASS] TestPongHandlerNullQ [PASS] TestServerResponse MonoTouchFixtures.Network.NWWebSocketMetadataTest : 0.5925 ms MonoTouchFixtures.Network.NWWebSocketOptionsTest [PASS] TestAddSubprotocol [PASS] TestAddSubprotocolNullValue [PASS] TestAutoReplyPing [PASS] TestClientRequenHandlerNullQ [PASS] TestClientRequestHandlerNullCallback [PASS] TestConstructorInvalidVersion [PASS] TestMaxMessageSize [PASS] TestSetHeader [PASS] TestSetHeaderNullName [PASS] TestSetHeaderNullValue [PASS] TestSkipHandShake MonoTouchFixtures.Network.NWWebSocketOptionsTest : 1.2593 ms MonoTouchFixtures.Network : 7313.4729 ms MonoTouchFixtures.NetworkExtension MonoTouchFixtures.NetworkExtension.OnDemandTest [PASS] EvaluateConnectionRule [PASS] EvaluateConnectionRule_Default [PASS] NEOnDemandRuleEvaluateConnection [PASS] OnDemandRuleConnect [PASS] OnDemandRuleDisconnect [PASS] OnDemandRuleIgnore MonoTouchFixtures.NetworkExtension.OnDemandTest : 2.884 ms MonoTouchFixtures.NetworkExtension.VpnManagerTest [PASS] Fields [PASS] SharedManager MonoTouchFixtures.NetworkExtension.VpnManagerTest : 1.2975 ms MonoTouchFixtures.NetworkExtension : 4.2005 ms MonoTouchFixtures.ObjCRuntime MonoTouchFixtures.ObjCRuntime.BlockSignatureTest [PASS] WithoutUserDelegateTypeAttribute [PASS] WithUserDelegateTypeAttribute MonoTouchFixtures.ObjCRuntime.BlockSignatureTest : 0.5203 ms MonoTouchFixtures.ObjCRuntime.BlocksTest [PASS] InvalidBlockTrampolines [PASS] SignatureA [PASS] SignatureB [PASS] SignatureC [PASS] TestSetupBlock MonoTouchFixtures.ObjCRuntime.BlocksTest : 0.8432 ms MonoTouchFixtures.ObjCRuntime.ClassTest [PASS] Bug33981 [PASS] Ctor [PASS] getClassTest [PASS] GetHandle [PASS] IsCustomType [PASS] Lookup [PASS] LookupTest MonoTouchFixtures.ObjCRuntime.ClassTest : 46.8902 ms MonoTouchFixtures.ObjCRuntime.DisposableObjectTest [PASS] CtorOwns [PASS] CtorOwnsVerify [PASS] DefaultCtor [PASS] Handle MonoTouchFixtures.ObjCRuntime.DisposableObjectTest : 0.534 ms MonoTouchFixtures.ObjCRuntime.DlfcnTest [PASS] GetVariables [PASS] OpenClose_libSystem MonoTouchFixtures.ObjCRuntime.DlfcnTest : 1.3877 ms MonoTouchFixtures.ObjCRuntime.ExceptionsTest [IGNORED] ManagedExceptionPassthrough : This test only works in debug mode in the simulator. at MonoTouchFixtures.ObjCRuntime.ExceptionsTest.ManagedExceptionPassthrough() [IGNORED] ObjCException : This test only works in debug mode in the simulator. at MonoTouchFixtures.ObjCRuntime.ExceptionsTest.ObjCException() MonoTouchFixtures.ObjCRuntime.ExceptionsTest : 0.9433 ms MonoTouchFixtures.ObjCRuntime.NativeHandleTest [PASS] Operators MonoTouchFixtures.ObjCRuntime.NativeHandleTest : 0.12 ms MonoTouchFixtures.ObjCRuntime.NativeRuntimeTest [PASS] XamarinCollapseStructName MonoTouchFixtures.ObjCRuntime.NativeRuntimeTest : 0.2285 ms MonoTouchFixtures.ObjCRuntime.NativeWeakTest [PASS] TestWeakReferences MonoTouchFixtures.ObjCRuntime.NativeWeakTest : 58.7431 ms MonoTouchFixtures.ObjCRuntime.ProtocolTest [PASS] Ctors MonoTouchFixtures.ObjCRuntime.ProtocolTest : 0.3985 ms MonoTouchFixtures.ObjCRuntime.RegistrarTest [PASS] BlockCollection [PASS] BlockReturnTest [PASS] Bug23289 [PASS] Bug34224 [PASS] ByrefParameter [PASS] ConformsToProtocolTest [PASS] ConformsToProtocolTest2 [PASS] ConstructorChaining [PASS] CustomAppDelegatePerformFetchTest [PASS] ExportedGenericsTest [PASS] FakeTypeTest [PASS] GenericAPI [PASS] GenericClassWithUnrelatedGenericDelegate [PASS] GenericVirtualTest [PASS] IdAsIntPtrTest [PASS] InOutProtocolMethodArgument {123, 345} [PASS] IProtocolTest [PASS] MethodEncodings [PASS] MethodEncodings2 [PASS] NSRangeOutParameter [PASS] OutNSErrorOnStack1 ptr: 0x1100f15c0 = 0x0 ptr: 0x0 [PASS] OutNSErrorOnStack2 ptr: 0x31d913150 = 0x0 ptr: 0x0 [PASS] OutOverriddenWithoutOutAttribute [PASS] PropertySetters [PASS] ProtocolArgument [PASS] ProtocolsTrimmedAway [PASS] RefEnumValues [PASS] RefOutTest_CFBundle [PASS] RefOutTest_Class [PASS] RefOutTest_ClassArray [PASS] RefOutTest_INSCoding [PASS] RefOutTest_INSCodingArray [PASS] RefOutTest_Int [PASS] RefOutTest_NSObject [PASS] RefOutTest_NSObjectArray [PASS] RefOutTest_NSValue [PASS] RefOutTest_NSValueArray [PASS] RefOutTest_Sel [PASS] RefOutTest_String [PASS] RefOutTest_StringArray [PASS] RegistrarRemoval [PASS] SelectorReturnValue [PASS] TestAction [PASS] TestCGPointParameter [PASS] TestConstrainedGenericType [PASS] TestConstrainedGenericType2 [PASS] TestCopyWithZone [PASS] TestCtors [PASS] TestGeneric [PASS] TestGenericUIView [PASS] TestINativeObject [PASS] TestInheritedProtocols [PASS] TestInheritedStaticMethods [PASS] TestINSCodingArray [PASS] TestInstanceMethodOnOpenGenericType [PASS] TestNativeEnum [PASS] TestNativeObjectArray [PASS] TestNestedGenericType [PASS] TestNonVirtualProperty [PASS] TestNSObjectArray [PASS] TestNullOutParameters [PASS] TestObjCProperties [PASS] TestOutNSString [PASS] TestProperties [PASS] TestProtocolAndRegister [PASS] TestProtocolRegistration [PASS] TestRegisteredName [PASS] TestRetainReturnValue [PASS] TestStaticProperties [PASS] TestStringArray [PASS] TestStructAndOut [PASS] TestThreadSafety [PASS] TestTypeEncodings [PASS] TestVirtual MonoTouchFixtures.ObjCRuntime.RegistrarTest : 1138.9821 ms MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated [IGNORED] NSNumberBindAs_Boolean_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_Boolean_Array_Overrides() [PASS] NSNumberBindAs_Boolean_Bindings [PASS] NSNumberBindAs_Boolean_Overrides [PASS] NSNumberBindAs_BooleanArray_Bindings [IGNORED] NSNumberBindAs_Byte_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_Byte_Array_Overrides() [PASS] NSNumberBindAs_Byte_Bindings [PASS] NSNumberBindAs_Byte_Overrides [PASS] NSNumberBindAs_ByteArray_Bindings [IGNORED] NSNumberBindAs_Double_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_Double_Array_Overrides() [PASS] NSNumberBindAs_Double_Bindings [PASS] NSNumberBindAs_Double_Overrides [PASS] NSNumberBindAs_DoubleArray_Bindings [IGNORED] NSNumberBindAs_Int16_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_Int16_Array_Overrides() [PASS] NSNumberBindAs_Int16_Bindings [PASS] NSNumberBindAs_Int16_Overrides [PASS] NSNumberBindAs_Int16Array_Bindings [IGNORED] NSNumberBindAs_Int32_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_Int32_Array_Overrides() [PASS] NSNumberBindAs_Int32_Bindings [PASS] NSNumberBindAs_Int32_Overrides [PASS] NSNumberBindAs_Int32Array_Bindings [IGNORED] NSNumberBindAs_Int64_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_Int64_Array_Overrides() [PASS] NSNumberBindAs_Int64_Bindings [PASS] NSNumberBindAs_Int64_Overrides [PASS] NSNumberBindAs_Int64Array_Bindings [PASS] NSNumberBindAs_nfloat_Array_Overrides [PASS] NSNumberBindAs_nfloat_Bindings [PASS] NSNumberBindAs_nfloat_Overrides [PASS] NSNumberBindAs_nfloatArray_Bindings [IGNORED] NSNumberBindAs_nint_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_nint_Array_Overrides() [PASS] NSNumberBindAs_nint_Bindings [PASS] NSNumberBindAs_nint_Overrides [PASS] NSNumberBindAs_nintArray_Bindings [IGNORED] NSNumberBindAs_NSStreamStatus_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_NSStreamStatus_Array_Overrides() [PASS] NSNumberBindAs_NSStreamStatus_Bindings [PASS] NSNumberBindAs_NSStreamStatus_Overrides [PASS] NSNumberBindAs_NSStreamStatusArray_Bindings [IGNORED] NSNumberBindAs_nuint_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_nuint_Array_Overrides() [PASS] NSNumberBindAs_nuint_Bindings [PASS] NSNumberBindAs_nuint_Overrides [PASS] NSNumberBindAs_nuintArray_Bindings [IGNORED] NSNumberBindAs_SByte_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_SByte_Array_Overrides() [PASS] NSNumberBindAs_SByte_Bindings [PASS] NSNumberBindAs_SByte_Overrides [PASS] NSNumberBindAs_SByteArray_Bindings [IGNORED] NSNumberBindAs_Single_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_Single_Array_Overrides() [PASS] NSNumberBindAs_Single_Bindings [PASS] NSNumberBindAs_Single_Overrides [PASS] NSNumberBindAs_SingleArray_Bindings [IGNORED] NSNumberBindAs_UInt16_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_UInt16_Array_Overrides() [PASS] NSNumberBindAs_UInt16_Bindings [PASS] NSNumberBindAs_UInt16_Overrides [PASS] NSNumberBindAs_UInt16Array_Bindings [IGNORED] NSNumberBindAs_UInt32_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_UInt32_Array_Overrides() [PASS] NSNumberBindAs_UInt32_Bindings [PASS] NSNumberBindAs_UInt32_Overrides [PASS] NSNumberBindAs_UInt32Array_Bindings [IGNORED] NSNumberBindAs_UInt64_Array_Overrides : https://github.com/dotnet/macios/issues/19781 at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.AssertIfIgnored(String testCase) at MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated.NSNumberBindAs_UInt64_Array_Overrides() [PASS] NSNumberBindAs_UInt64_Bindings [PASS] NSNumberBindAs_UInt64_Overrides [PASS] NSNumberBindAs_UInt64Array_Bindings [PASS] NSStringBindAs_SecKeyAlgorithm_Array_Overrides [PASS] NSStringBindAs_SecKeyAlgorithm_Bindings [PASS] NSStringBindAs_SecKeyAlgorithm_Overrides [PASS] NSStringBindAs_SecKeyAlgorithmArray_Bindings [PASS] NSValueBindAs_CATransform3D_Array_Overrides [PASS] NSValueBindAs_CATransform3D_Bindings [PASS] NSValueBindAs_CATransform3D_Overrides [PASS] NSValueBindAs_CATransform3DArray_Bindings [PASS] NSValueBindAs_CGAffineTransform_Array_Overrides [PASS] NSValueBindAs_CGAffineTransform_Bindings [PASS] NSValueBindAs_CGAffineTransform_Overrides [PASS] NSValueBindAs_CGAffineTransformArray_Bindings [PASS] NSValueBindAs_CGPoint_Array_Overrides [PASS] NSValueBindAs_CGPoint_Bindings [PASS] NSValueBindAs_CGPoint_Overrides [PASS] NSValueBindAs_CGPointArray_Bindings [PASS] NSValueBindAs_CGRect_Array_Overrides [PASS] NSValueBindAs_CGRect_Bindings [PASS] NSValueBindAs_CGRect_Overrides [PASS] NSValueBindAs_CGRectArray_Bindings [PASS] NSValueBindAs_CGSize_Array_Overrides [PASS] NSValueBindAs_CGSize_Bindings [PASS] NSValueBindAs_CGSize_Overrides [PASS] NSValueBindAs_CGSizeArray_Bindings [PASS] NSValueBindAs_CGVector_Array_Overrides [PASS] NSValueBindAs_CGVector_Bindings [PASS] NSValueBindAs_CGVector_Overrides [PASS] NSValueBindAs_CGVectorArray_Bindings [PASS] NSValueBindAs_CLLocationCoordinate2D_Array_Overrides [PASS] NSValueBindAs_CLLocationCoordinate2D_Bindings [PASS] NSValueBindAs_CLLocationCoordinate2D_Overrides [PASS] NSValueBindAs_CLLocationCoordinate2DArray_Bindings [PASS] NSValueBindAs_CMTime_Array_Overrides [PASS] NSValueBindAs_CMTime_Bindings [PASS] NSValueBindAs_CMTime_Overrides [PASS] NSValueBindAs_CMTimeArray_Bindings [PASS] NSValueBindAs_CMTimeMapping_Array_Overrides [PASS] NSValueBindAs_CMTimeMapping_Bindings [PASS] NSValueBindAs_CMTimeMapping_Overrides [PASS] NSValueBindAs_CMTimeMappingArray_Bindings [PASS] NSValueBindAs_CMTimeRange_Array_Overrides [PASS] NSValueBindAs_CMTimeRange_Bindings [PASS] NSValueBindAs_CMTimeRange_Overrides [PASS] NSValueBindAs_CMTimeRangeArray_Bindings [PASS] NSValueBindAs_CMVideoDimensions_Array_Overrides [PASS] NSValueBindAs_CMVideoDimensions_Bindings [PASS] NSValueBindAs_CMVideoDimensions_Overrides [PASS] NSValueBindAs_CMVideoDimensionsArray_Bindings [PASS] NSValueBindAs_MKCoordinateSpan_Array_Overrides [PASS] NSValueBindAs_MKCoordinateSpan_Bindings [PASS] NSValueBindAs_MKCoordinateSpan_Overrides [PASS] NSValueBindAs_MKCoordinateSpanArray_Bindings [PASS] NSValueBindAs_NSDirectionalEdgeInsets_Array_Overrides [PASS] NSValueBindAs_NSDirectionalEdgeInsets_Bindings [PASS] NSValueBindAs_NSDirectionalEdgeInsets_Overrides [PASS] NSValueBindAs_NSDirectionalEdgeInsetsArray_Bindings [PASS] NSValueBindAs_NSRange_Array_Overrides [PASS] NSValueBindAs_NSRange_Bindings [PASS] NSValueBindAs_NSRange_Overrides [PASS] NSValueBindAs_NSRangeArray_Bindings [PASS] NSValueBindAs_SCNMatrix4_Array_Overrides [PASS] NSValueBindAs_SCNMatrix4_Bindings [PASS] NSValueBindAs_SCNMatrix4_Overrides [PASS] NSValueBindAs_SCNMatrix4Array_Bindings [PASS] NSValueBindAs_SCNVector3_Array_Overrides [PASS] NSValueBindAs_SCNVector3_Bindings [PASS] NSValueBindAs_SCNVector3_Overrides [PASS] NSValueBindAs_SCNVector3Array_Bindings [PASS] NSValueBindAs_SCNVector4_Array_Overrides [PASS] NSValueBindAs_SCNVector4_Bindings [PASS] NSValueBindAs_SCNVector4_Overrides [PASS] NSValueBindAs_SCNVector4Array_Bindings [PASS] NSValueBindAs_UIEdgeInsets_Array_Overrides [PASS] NSValueBindAs_UIEdgeInsets_Bindings [PASS] NSValueBindAs_UIEdgeInsets_Overrides [PASS] NSValueBindAs_UIEdgeInsetsArray_Bindings [PASS] NSValueBindAs_UIOffset_Array_Overrides [PASS] NSValueBindAs_UIOffset_Bindings [PASS] NSValueBindAs_UIOffset_Overrides [PASS] NSValueBindAs_UIOffsetArray_Bindings [PASS] Test_c [PASS] Test_cc [PASS] Test_ccc [PASS] Test_cccc [PASS] Test_cfcf [PASS] Test_d [PASS] Test_dd [PASS] Test_ddd [PASS] Test_dddd [PASS] Test_ddddd [PASS] Test_ddi [PASS] Test_dff [PASS] Test_di [PASS] Test_didi [PASS] Test_dldl [PASS] Test_f [PASS] Test_fcccc [PASS] Test_fccfcc [PASS] Test_fcfc [PASS] Test_ff [PASS] Test_ffcccc [PASS] Test_ffd [PASS] Test_fff [PASS] Test_fffcccc [PASS] Test_ffff [PASS] Test_fffff [PASS] Test_ffi [PASS] Test_ffii [PASS] Test_fi [PASS] Test_fif [PASS] Test_fifi [PASS] Test_fii [PASS] Test_i [PASS] Test_id [PASS] Test_idi [PASS] Test_idid [PASS] Test_if [PASS] Test_iff [PASS] Test_ifi [PASS] Test_ifif [PASS] Test_ii [PASS] Test_iid [PASS] Test_iif [PASS] Test_iiff [PASS] Test_iii [PASS] Test_iiii [PASS] Test_iiiii [PASS] Test_l [PASS] Test_ldld [PASS] Test_ll [PASS] Test_lll [PASS] Test_llld [PASS] Test_lllf [PASS] Test_llll [PASS] Test_lllll [PASS] Test_s [PASS] Test_scfscf [PASS] Test_sfccfs [PASS] Test_sfsf [PASS] Test_ss [PASS] Test_sss [PASS] Test_ssss MonoTouchFixtures.ObjCRuntime.RegistrarTestGenerated : 55.2067 ms MonoTouchFixtures.ObjCRuntime.ResourcesTest [PASS] Embedded MonoTouchFixtures.ObjCRuntime.ResourcesTest : 4.6878 ms MonoTouchFixtures.ObjCRuntime.RuntimeTest [PASS] ConformsToProtocolCreatesManagedInstance [PASS] ConnectMethod [PASS] ConnectMethod1 Tested! [PASS] ConnectMethod2 [PASS] ConnectMethod3 [PASS] ConnectMethodTest [PASS] CurrentDirectory [PASS] CurrentDomain_BaseDirectory_Test [PASS] FinalizationRaceCondition [PASS] GetINativeObject_ForcedType [PASS] GetINativeObjectTest [PASS] GetNSObject_Different_Class [PASS] GetNSObject_IntPtrZero [PASS] GetNSObject_Posing_Class [PASS] GetNSObject_Subclass [PASS] GetNSObject_T_SameHandleDifferentInstances [PASS] IntPtrBoolCtor_1 [PASS] IntPtrCtor_1 [PASS] IntPtrCtor_2 [PASS] IntPtrCtor_3 [PASS] MX8029 [PASS] NSAutoreleasePoolInThread [PASS] NSAutoreleasePoolInThreadPool [PASS] OriginalWorkingDirectoryTest [PASS] RegisterAssembly_null MonoTouchFixtures.ObjCRuntime.RuntimeTest.ResurrectedObjectsDisposedTest [PASS] ResurrectedObjectsDisposedTest(Foundation.NSObject) [PASS] ResurrectedObjectsDisposedTest(MonoTouchFixtures.ObjCRuntime.RuntimeTest+ResurrectedObjectsDisposedTestClass) MonoTouchFixtures.ObjCRuntime.RuntimeTest.ResurrectedObjectsDisposedTest : 12033.5287 ms [PASS] ToggleRef_NonToggledObjectsShouldBeCollected [PASS] ToggleRef_ToggledObjectsShouldNotBeCollected [PASS] UsableUntilDead 11/20/2025 7:46:49 PM Notified MonoTouchFixtures.ObjCRuntime.RuntimeTest : 24440.6714 ms MonoTouchFixtures.ObjCRuntime.RuntimeTest_GetINativeObjectTest [PASS] GetINativeObjectTest_INativeObject_Owns [PASS] GetINativeObjectTest_INativeObject_Unowned [PASS] GetINativeObjectTest_NSObject_Owns [PASS] GetINativeObjectTest_NSObject_Unowned MonoTouchFixtures.ObjCRuntime.RuntimeTest_GetINativeObjectTest : 0.9987 ms MonoTouchFixtures.ObjCRuntime.StrongEnumTest [IGNORED] GetConstant : This test only executes using the latest OS version (26.1) at TestRuntime.AssertMatchingOSVersionAndSdkVersion() at MonoTouchFixtures.ObjCRuntime.StrongEnumTest.GetConstant() MonoTouchFixtures.ObjCRuntime.StrongEnumTest : 0.4387 ms MonoTouchFixtures.ObjCRuntime.ToggleRefRetainDeadlockTest [PASS] RunTest MonoTouchFixtures.ObjCRuntime.ToggleRefRetainDeadlockTest : 555.775 ms MonoTouchFixtures.ObjCRuntime.TrampolineTest [PASS] ArrayTest [PASS] DoubleReturnTest [PASS] FloatingPointStretTrampolineTest [PASS] FloatReturnTest [PASS] IntPtrTest [PASS] LoooongTest [PASS] OutParamTest [PASS] OutValueTypeTest [PASS] StretTrampolineTest [PASS] X64ArgumentOverflow MonoTouchFixtures.ObjCRuntime.TrampolineTest : 2.4129 ms MonoTouchFixtures.ObjCRuntime.TrampolineTestGenerated [PASS] Test_c [PASS] Test_cc [PASS] Test_ccc [PASS] Test_cccc [PASS] Test_cfcf [PASS] Test_d [PASS] Test_dd [PASS] Test_ddd [PASS] Test_dddd [PASS] Test_ddddd [PASS] Test_ddi [PASS] Test_dff [PASS] Test_di [PASS] Test_didi [PASS] Test_dldl [PASS] Test_f [PASS] Test_fcccc [PASS] Test_fccfcc [PASS] Test_fcfc [PASS] Test_ff [PASS] Test_ffcccc [PASS] Test_ffd [PASS] Test_fff [PASS] Test_fffcccc [PASS] Test_ffff [PASS] Test_fffff [PASS] Test_ffi [PASS] Test_ffii [PASS] Test_fi [PASS] Test_fif [PASS] Test_fifi [PASS] Test_fii [PASS] Test_i [PASS] Test_id [PASS] Test_idi [PASS] Test_idid [PASS] Test_if [PASS] Test_iff [PASS] Test_ifi [PASS] Test_ifif [PASS] Test_ii [PASS] Test_iid [PASS] Test_iif [PASS] Test_iiff [PASS] Test_iii [PASS] Test_iiii [PASS] Test_iiiii [PASS] Test_l [PASS] Test_ldld [PASS] Test_ll [PASS] Test_lll [PASS] Test_llld [PASS] Test_lllf [PASS] Test_llll [PASS] Test_lllll [PASS] Test_s [PASS] Test_scfscf [PASS] Test_sfccfs [PASS] Test_sfsf [PASS] Test_ss [PASS] Test_sss [PASS] Test_ssss MonoTouchFixtures.ObjCRuntime.TrampolineTestGenerated : 269.1569 ms MonoTouchFixtures.ObjCRuntime : 26579.0702 ms MonoTouchFixtures.PassKit MonoTouchFixtures.PassKit.AddPassesViewControllerTest [INCONCLUSIVE] BoardingPass : PassKit does not work on iPads at MonoTouchFixtures.PassKit.AddPassesViewControllerTest.BoardingPass() [INCONCLUSIVE] InitWithNibNameTest : PassKit does not work on iPads at MonoTouchFixtures.PassKit.AddPassesViewControllerTest.InitWithNibNameTest() MonoTouchFixtures.PassKit.AddPassesViewControllerTest : 0.4512 ms MonoTouchFixtures.PassKit.ObjectTest [PASS] Constructor MonoTouchFixtures.PassKit.ObjectTest : 0.1682 ms MonoTouchFixtures.PassKit.PassLibraryTest [PASS] Defaults [PASS] Fields MonoTouchFixtures.PassKit.PassLibraryTest : 34.8983 ms MonoTouchFixtures.PassKit.PassTest [PASS] BoardingPass [PASS] Defaults [PASS] Fields MonoTouchFixtures.PassKit.PassTest : 13.2935 ms MonoTouchFixtures.PassKit.PKJapanIndividualNumberCardMetadataTest [PASS] Ctor_CardConfigurationIdentifier [PASS] Ctor_CardTemplateIdentifier MonoTouchFixtures.PassKit.PKJapanIndividualNumberCardMetadataTest : 1.7976 ms MonoTouchFixtures.PassKit.PKLabeledValueTest [PASS] Constructor MonoTouchFixtures.PassKit.PKLabeledValueTest : 0.1919 ms MonoTouchFixtures.PassKit.PKPassTest [PASS] GetLocalizedValueNull MonoTouchFixtures.PassKit.PKPassTest : 0.0988 ms MonoTouchFixtures.PassKit.PKPaymentRequestTest [PASS] CheckDefaultNulls [PASS] RequiredBillingContactFields [PASS] WeakRequiredBillingContactFields MonoTouchFixtures.PassKit.PKPaymentRequestTest : 1.6383 ms MonoTouchFixtures.PassKit.PKPaymentSummaryItemTest [PASS] CheckDefaultNulls MonoTouchFixtures.PassKit.PKPaymentSummaryItemTest : 0.1896 ms MonoTouchFixtures.PassKit : 52.7998 ms MonoTouchFixtures.PdfKit MonoTouchFixtures.PdfKit.PdfAnnotationTest [PASS] QuadrilateralPoints MonoTouchFixtures.PdfKit.PdfAnnotationTest : 0.6097 ms MonoTouchFixtures.PdfKit : 0.614 ms MonoTouchFixtures.Phase MonoTouchFixtures.Phase.PhaseAmbientMixerDefinitionTest [IGNORED] TestConstructor : This test only runs on device. at MonoTouchFixtures.Phase.PhaseAmbientMixerDefinitionTest.Setup() MonoTouchFixtures.Phase.PhaseAmbientMixerDefinitionTest : 0.2037 ms MonoTouchFixtures.Phase.PhaseEnvelopeSegmentTest [IGNORED] ConstructorTest : This test only runs on device. at MonoTouchFixtures.Phase.PhaseEnvelopeSegmentTest.Setup() MonoTouchFixtures.Phase.PhaseEnvelopeSegmentTest : 0.155 ms MonoTouchFixtures.Phase.PhaseEnvelopeTest [IGNORED] ConstructorTest : This test only runs on device. at MonoTouchFixtures.Phase.PhaseEnvelopeTest.Setup() MonoTouchFixtures.Phase.PhaseEnvelopeTest : 0.0886 ms MonoTouchFixtures.Phase.PhaseObjectTest [IGNORED] ForwardTest : This test only runs on device. at MonoTouchFixtures.Phase.PhaseObjectTest.Setup() [IGNORED] RightTest : This test only runs on device. at MonoTouchFixtures.Phase.PhaseObjectTest.Setup() [IGNORED] TransformTest : This test only runs on device. at MonoTouchFixtures.Phase.PhaseObjectTest.Setup() [IGNORED] UpTest : This test only runs on device. at MonoTouchFixtures.Phase.PhaseObjectTest.Setup() [IGNORED] WorldTransform : This test only runs on device. at MonoTouchFixtures.Phase.PhaseObjectTest.Setup() MonoTouchFixtures.Phase.PhaseObjectTest : 0.3494 ms MonoTouchFixtures.Phase : 0.8323 ms MonoTouchFixtures.Photos MonoTouchFixtures.Photos.FetchResultTest [INCONCLUSIVE] FetchResultIndex : Requires access to the photo library at MonoTouchFixtures.Photos.FetchResultTest.Setup() [INCONCLUSIVE] FetchResultObjectsAt : Requires access to the photo library at MonoTouchFixtures.Photos.FetchResultTest.Setup() [INCONCLUSIVE] FetchResultToArray : Requires access to the photo library at MonoTouchFixtures.Photos.FetchResultTest.Setup() MonoTouchFixtures.Photos.FetchResultTest : 69.4682 ms MonoTouchFixtures.Photos.PHLivePhotoEditingContextTest [PASS] FrameProcessingBlock2 [PASS] Linker MonoTouchFixtures.Photos.PHLivePhotoEditingContextTest : 0.9565 ms MonoTouchFixtures.Photos : 70.4366 ms MonoTouchFixtures.PushKit MonoTouchFixtures.PushKit.PushRegistryTest [PASS] CtorDispatchQueue MonoTouchFixtures.PushKit.PushRegistryTest : 0.2222 ms MonoTouchFixtures.PushKit : 0.234 ms MonoTouchFixtures.QuickLook MonoTouchFixtures.QuickLook.PreviewControllerTest [PASS] Defaults [PASS] DelegateEvents MonoTouchFixtures.QuickLook.PreviewControllerTest : 10.5744 ms MonoTouchFixtures.QuickLook : 10.6007 ms MonoTouchFixtures.SafariServices MonoTouchFixtures.SafariServices.ReadingListTest [IGNORED] DefaultReadingList : This test adds two entries every time it's executed to the global reading list in Safari. For people who use their reading lists this becomes slightly annoying. [PASS] SupportsUrl MonoTouchFixtures.SafariServices.ReadingListTest : 0.348 ms MonoTouchFixtures.SafariServices : 0.3601 ms MonoTouchFixtures.SceneKit MonoTouchFixtures.SceneKit.ActionTest [PASS] TimingFunction_5072 MonoTouchFixtures.SceneKit.ActionTest : 0.1544 ms MonoTouchFixtures.SceneKit.AvoidOccluderConstraintTest [PASS] Delegate_Nullability MonoTouchFixtures.SceneKit.AvoidOccluderConstraintTest : 0.3847 ms MonoTouchFixtures.SceneKit.GeometrySourceTest [IGNORED] FromMetalBuffer : This test only runs on device. at TestRuntime.AssertDevice(String message) at MonoTouchFixtures.SceneKit.GeometrySourceTest.FromMetalBuffer() MonoTouchFixtures.SceneKit.GeometrySourceTest : 0.197 ms MonoTouchFixtures.SceneKit.MorpherTest [PASS] Targets_Nullability MonoTouchFixtures.SceneKit.MorpherTest : 0.2182 ms MonoTouchFixtures.SceneKit.NodeTest [PASS] AddAnimation [PASS] AddAnimation_Overload MonoTouchFixtures.SceneKit.NodeTest : 0.616 ms MonoTouchFixtures.SceneKit.SCNMatrix4Test [PASS] Constructor_CATransform3d [PASS] Constructor_Elements [PASS] Constructor_RowVectors [PASS] CreateFromAxisAngle [PASS] CreateFromAxisAngle_double_Out [PASS] CreateFromAxisAngle_float_Out [PASS] CreateFromAxisAngle_pfloat_Out [PASS] CreateFromColumns [PASS] CreateFromColumns_Out [PASS] CreateOrthographic [PASS] CreateOrthographic_Out [PASS] CreateOrthographicOffCenter [PASS] CreateOrthographicOffCenter_Out [PASS] CreatePerspectiveFieldOfView [PASS] CreatePerspectiveFieldOfView_Out [PASS] CreatePerspectiveOffCenter [PASS] CreatePerspectiveOffCenter_Out [PASS] CreateRotationX [PASS] CreateRotationX_NodeComparison [PASS] CreateRotationX_Out [PASS] CreateRotationY [PASS] CreateRotationY_Out [PASS] CreateRotationZ [PASS] CreateRotationZ_Out [PASS] CreateTranslation [PASS] CreateTranslation_Out [PASS] CreateTranslation_Vector [PASS] CreateTranslation_Vector_Out [PASS] CreateTranslationAndTransformPosition [PASS] CreateTranslations_out_floats [PASS] CreateTranslations_out_SCNVector3 [PASS] CreateTranslations_ret_floats [PASS] CreateTranslations_ret_SCNVector3 [PASS] Determinant [PASS] Elements [PASS] Identity [PASS] IEquatable_Equals [PASS] Invert [PASS] LookAt_Elements [PASS] LookAt_Vectors [PASS] Mult [PASS] Mult_ByRef [PASS] Object_Equals [PASS] Operator_Equals [PASS] Operator_Multiply [PASS] Operator_NotEquals [PASS] Rotate [PASS] Rotate_d [PASS] Rows [PASS] Scale [PASS] Scale_3 [PASS] Scale_Vector [PASS] SCNMatrix4Translate [PASS] Static_Invert [PASS] Static_Transpose [PASS] Static_Transpose_ByRef [PASS] ToStringTest [PASS] TranslationPosition_out [PASS] TranslationPosition_ret [PASS] Transpose MonoTouchFixtures.SceneKit.SCNMatrix4Test : 2.1472 ms MonoTouchFixtures.SceneKit.SCNVector3Test [PASS] Transform [PASS] Transform_out [PASS] TransformNormal [PASS] TransformNormal_out [PASS] TransformNormalInverse [PASS] TransformNormalInverse_out [PASS] TransformPerspective [PASS] TransformPerspective_out [PASS] TransformPosition [PASS] TransformPosition_out [PASS] TransformVector [PASS] TransformVector_out MonoTouchFixtures.SceneKit.SCNVector3Test : 0.2657 ms MonoTouchFixtures.SceneKit.SCNVector4Test [PASS] Transform [PASS] Transform_out MonoTouchFixtures.SceneKit.SCNVector4Test : 0.0482 ms MonoTouchFixtures.SceneKit.SCNViewTests [PASS] NullOverlaySceneTest MonoTouchFixtures.SceneKit.SCNViewTests : 3.2313 ms MonoTouchFixtures.SceneKit.StructTest [PASS] Matrix [PASS] Quaternion [PASS] Vector MonoTouchFixtures.SceneKit.StructTest : 0.5025 ms MonoTouchFixtures.SceneKit : 7.8051 ms MonoTouchFixtures.ScreenTime MonoTouchFixtures.ScreenTime.STWebHistoryTest [PASS] Create_WithBundleIdentifier [PASS] Create_WithBundleIdentifierAndProfile MonoTouchFixtures.ScreenTime.STWebHistoryTest : 0.4594 ms MonoTouchFixtures.ScreenTime : 0.4637 ms MonoTouchFixtures.Security MonoTouchFixtures.Security.CertificateTest [PASS] GenerateKeyPairTest [PASS] MailNSData [PASS] MailRaw [PASS] MailX1 [IGNORED] MailX2 : https://bugzilla.xamarin.com/show_bug.cgi?id=39952 [PASS] X2 MonoTouchFixtures.Security.CertificateTest : 36.8955 ms MonoTouchFixtures.Security.IdentityTest [PASS] AccessCertificates [IGNORED] Create : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.Security.IdentityTest.Create() [PASS] I2 [PASS] Identity MonoTouchFixtures.Security.IdentityTest : 7.3432 ms MonoTouchFixtures.Security.ImportExportTest [PASS] P12_AuthFailed [PASS] P12_Success MonoTouchFixtures.Security.ImportExportTest : 2.4073 ms MonoTouchFixtures.Security.KeyChainTest [IGNORED] Add_Certificate : This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile. [IGNORED] AddQueryRemove_Identity : This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile. [IGNORED] CheckId : This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile. [PASS] QueryAsData [PASS] QueryAsDataArray [IGNORED] SecItemAdd_Identity : This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile. MonoTouchFixtures.Security.KeyChainTest : 4.4458 ms MonoTouchFixtures.Security.KeyTest [IGNORED] BenchmarkManaged4096 : Just to compare performance with BenchmarkNative4096 [IGNORED] BenchmarkNative4096 : Just to compare performance with BenchmarkManaged4096 [PASS] CreateRandomKeyTest [PASS] EC [PASS] ECSecPrimeRandom [PASS] Encrypt_Empty [PASS] Encrypt_New [PASS] Encrypt_Old [PASS] EncryptTooLarge [PASS] GenerateKeyPairTooLargeRSA [PASS] RoundtripRSA1024OAEP [PASS] RoundtripRSAMinPKCS1 [PASS] RSA [PASS] SignVerifyECSHA1 [PASS] SignVerifyRSAMinPKCS1SHA1 MonoTouchFixtures.Security.KeyTest : 61.9591 ms MonoTouchFixtures.Security.ProtocolOptionsTest [PASS] Defaults [PASS] Equals [PASS] NewTlsOptions MonoTouchFixtures.Security.ProtocolOptionsTest : 0.5037 ms MonoTouchFixtures.Security.RecordTest [IGNORED] Accessible_17579 : This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile. [IGNORED] AuthenticationType_17579 : This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile. [IGNORED] DeskCase_83099_InmutableDictionary : This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile. [PASS] Identity [IGNORED] IdentityRecordTest : This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile. [PASS] KeyRecordTest [PASS] Match [IGNORED] Protocol_17579 : This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile. [PASS] SecRecordRecordTest MonoTouchFixtures.Security.RecordTest : 18.092 ms MonoTouchFixtures.Security.SecPolicyTest [PASS] BasicX509Policy [PASS] CreateUnknownPolicy [PASS] CreateWellKnownPolicies [PASS] RevocationPolicy [PASS] SslClient [PASS] SslServer [PASS] SslServerNoHost MonoTouchFixtures.Security.SecPolicyTest : 2.5256 ms MonoTouchFixtures.Security.SecProtocolMetadataTest [PASS] TlsDefaults MonoTouchFixtures.Security.SecProtocolMetadataTest : 136.5336 ms MonoTouchFixtures.Security.SecSharedCredentialTest [PASS] AddSharedWebCredentialNotNullPassword [PASS] AddSharedWebCredentialNullAccount [PASS] AddSharedWebCredentialNullDomain [PASS] AddSharedWebCredentialNullPassword [PASS] CreateSharedWebCredentialPassword MonoTouchFixtures.Security.SecSharedCredentialTest : 0.7215 ms MonoTouchFixtures.Security.SecStatusCodeTest [PASS] ErrorDescriptionTest MonoTouchFixtures.Security.SecStatusCodeTest : 0.1602 ms MonoTouchFixtures.Security.SecureTransportTest [PASS] DatagramDefaults [PASS] StreamDefaults [PASS] Tls12 MonoTouchFixtures.Security.SecureTransportTest : 334.4146 ms MonoTouchFixtures.Security.TrustTest [PASS] Basic_Leaf_Only [PASS] Client_Leaf_Only [PASS] HostName_Leaf_Only [PASS] NoHostName [PASS] Timestamps [PASS] Trust_FullChain [PASS] Trust_Leaf_Only [PASS] Trust_NoRoot [PASS] Trust2_FullChain [PASS] Trust2_Leaf_Only [PASS] Trust2_NoRoot MonoTouchFixtures.Security.TrustTest : 443.7565 ms MonoTouchFixtures.Security : 1049.937 ms MonoTouchFixtures.SharedWithYouCore MonoTouchFixtures.SharedWithYouCore.SWCollaborationMetadataTests MonoTouchFixtures.SharedWithYouCore.SWCollaborationMetadataTests.IdentifierTypeConstructorTest [PASS] IdentifierTypeConstructorTest(Local,"_InitWithLocalIdentifier") [PASS] IdentifierTypeConstructorTest(Collaboration,"_InitWithCollaborationIdentifier") MonoTouchFixtures.SharedWithYouCore.SWCollaborationMetadataTests.IdentifierTypeConstructorTest : 3.2127 ms [PASS] LocalIdentifierConstructorTest MonoTouchFixtures.SharedWithYouCore.SWCollaborationMetadataTests : 3.7892 ms MonoTouchFixtures.SharedWithYouCore : 3.8006 ms MonoTouchFixtures.Simd MonoTouchFixtures.Simd.MatrixFloat2x2Test [PASS] Identity [PASS] ToStringTest MonoTouchFixtures.Simd.MatrixFloat2x2Test : 0.289 ms MonoTouchFixtures.Simd.MatrixFloat3x3Test [PASS] Identity [PASS] ToStringTest MonoTouchFixtures.Simd.MatrixFloat3x3Test : 0.2498 ms MonoTouchFixtures.Simd.MatrixFloat4x4Test [PASS] Identity MonoTouchFixtures.Simd.MatrixFloat4x4Test : 0.2201 ms MonoTouchFixtures.Simd.NMatrix4dTest [PASS] Identity MonoTouchFixtures.Simd.NMatrix4dTest : 0.2222 ms MonoTouchFixtures.Simd.NMatrix4x3Test [PASS] ElementConstructor [PASS] Elements [PASS] Equality_Operator [PASS] Equals_Matrix [PASS] Equals_Object [PASS] Inequality_Operator [PASS] ToStringTest MonoTouchFixtures.Simd.NMatrix4x3Test : 0.8067 ms MonoTouchFixtures.Simd.NVector3dTest [PASS] ElementConstructor [PASS] Equality_Operator [PASS] Equals_Object [PASS] Equals_Vector [PASS] Inequality_Operator [PASS] ToStringTest MonoTouchFixtures.Simd.NVector3dTest : 0.4579 ms MonoTouchFixtures.Simd.VectorByte16Test [PASS] ArrayConstructor [PASS] ArrayConstructor_LargeArray [PASS] ArrayConstructor_Null [PASS] ArrayConstructor_PartialArray [PASS] AsSpan [PASS] DefaultConstructor [PASS] Equality_Operator [PASS] Equals_Object [PASS] Equals_Vector [PASS] GetHashCode_DifferentVectors [PASS] GetHashCode_SameVectors [PASS] Indexer_Get [PASS] Indexer_OutOfRange_Negative [PASS] Indexer_OutOfRange_TooLarge [PASS] Indexer_Set [PASS] Inequality_Operator [PASS] ToStringTest [PASS] Zero_Property MonoTouchFixtures.Simd.VectorByte16Test : 1.1302 ms MonoTouchFixtures.Simd.VectorFloat3Test [PASS] ElementConstructor [PASS] Equality_Operator [PASS] Equals_Object [PASS] Equals_Vector [PASS] Explicit_Operator_FromVector3 [PASS] Explicit_Operator_ToVector3 [PASS] Inequality_Operator [PASS] ToStringTest MonoTouchFixtures.Simd.VectorFloat3Test : 0.4585 ms MonoTouchFixtures.Simd : 3.8678 ms MonoTouchFixtures.SpriteKit MonoTouchFixtures.SpriteKit.FieldNodeTest [PASS] CreateCustomField [PASS] CreateLinearGravityField [PASS] CreateRadialGravityField [PASS] CreateVelocityField MonoTouchFixtures.SpriteKit.FieldNodeTest : 1.7961 ms MonoTouchFixtures.SpriteKit.KeyframeSequenceTest [PASS] Ctor_Arrays [PASS] Ctor_Arrays_Null [PASS] Ctor_Capacity MonoTouchFixtures.SpriteKit.KeyframeSequenceTest : 2.3339 ms MonoTouchFixtures.SpriteKit.PhysicsBodyTest [PASS] BodyWithEdgeLoopFromRect MonoTouchFixtures.SpriteKit.PhysicsBodyTest : 0.1655 ms MonoTouchFixtures.SpriteKit.PhysicsJointFixedTest [PASS] Create MonoTouchFixtures.SpriteKit.PhysicsJointFixedTest : 0.8639 ms MonoTouchFixtures.SpriteKit.PhysicsJointLimitTest [PASS] Create MonoTouchFixtures.SpriteKit.PhysicsJointLimitTest : 0.5415 ms MonoTouchFixtures.SpriteKit.PhysicsWorldTest [PASS] SampleFields MonoTouchFixtures.SpriteKit.PhysicsWorldTest : 0.2372 ms MonoTouchFixtures.SpriteKit.SK3DNodeTest [IGNORED] ProjectPoint : This doesn't seem to work properly in the iOS 9+ or macOS 10.11+ at MonoTouchFixtures.SpriteKit.SK3DNodeTest.ProjectPoint() [IGNORED] UnprojectPoint : This doesn't seem to work properly in the iOS 9 at MonoTouchFixtures.SpriteKit.SK3DNodeTest.UnprojectPoint() MonoTouchFixtures.SpriteKit.SK3DNodeTest : 0.5129 ms MonoTouchFixtures.SpriteKit.SKShapeNodeTest [PASS] FromPointsOffsetTest [PASS] FromPointsTest [PASS] FromSplinePointsOffsetTest [PASS] FromSplinePointsTest MonoTouchFixtures.SpriteKit.SKShapeNodeTest : 0.792 ms MonoTouchFixtures.SpriteKit.SKTransformNodeTest [PASS] EulerAngles [PASS] QuaternionTest [PASS] RotationMatrix MonoTouchFixtures.SpriteKit.SKTransformNodeTest : 0.8948 ms MonoTouchFixtures.SpriteKit.SpriteNodeTest [PASS] Color [PASS] Ctor [PASS] CtorColor [PASS] CtorName [PASS] CtorTexture [PASS] CtorTextureColor [PASS] Texture MonoTouchFixtures.SpriteKit.SpriteNodeTest : 4.5597 ms MonoTouchFixtures.SpriteKit.TextureAtlasTest [PASS] Empty MonoTouchFixtures.SpriteKit.TextureAtlasTest : 1.2114 ms MonoTouchFixtures.SpriteKit.TextureTest [PASS] Atlas_MissingResource MonoTouchFixtures.SpriteKit.TextureTest : 3.1177 ms MonoTouchFixtures.SpriteKit.UniformTest [PASS] Create [PASS] Ctors MonoTouchFixtures.SpriteKit.UniformTest : 1.6328 ms MonoTouchFixtures.SpriteKit.WarpGeometryGridTest [PASS] CreateTest [PASS] GetGridByReplacingDestPositionsTest [PASS] GetGridByReplacingSourcePositionsTest [PASS] SKWarpGeometryGridTest MonoTouchFixtures.SpriteKit.WarpGeometryGridTest : 0.506 ms MonoTouchFixtures.SpriteKit : 19.2314 ms MonoTouchFixtures.StoreKit MonoTouchFixtures.StoreKit.ReceiptRefreshRequestTest [PASS] TerminateForInvalidReceipt MonoTouchFixtures.StoreKit.ReceiptRefreshRequestTest : 0.2351 ms MonoTouchFixtures.StoreKit.SKCloudServiceSetupOptionsTest [PASS] ActionTest MonoTouchFixtures.StoreKit.SKCloudServiceSetupOptionsTest : 0.3632 ms MonoTouchFixtures.StoreKit : 0.6059 ms MonoTouchFixtures.Symbols [IGNORED] FunctionNames : This test only runs on device. at MonoTouchFixtures.Symbols.FunctionNames() MonoTouchFixtures.Symbols : 0.1847 ms MonoTouchFixtures.System MonoTouchFixtures.System.NativeTypes [PASS] CompareTo [PASS] Equals [PASS] IsInfinity [PASS] IsNan [PASS] IsNegativeInfinity [PASS] IsPositiveInfinity [PASS] NaN_Cast [PASS] NegativeInfinity_Cast [PASS] PositiveInfinity_Cast MonoTouchFixtures.System.NativeTypes : 0.3537 ms MonoTouchFixtures.System : 0.3588 ms MonoTouchFixtures.SystemConfiguration MonoTouchFixtures.SystemConfiguration.CaptiveNetworkTest [PASS] Fields [PASS] MarkPortalOffline [PASS] MarkPortalOffline_Null [PASS] MarkPortalOnline [PASS] MarkPortalOnline_Null [PASS] SetSupportedSSIDs [PASS] SetSupportedSSIDs_Null [PASS] TryCopyCurrentNetworkInfo [PASS] TryCopyCurrentNetworkInfo_Null [PASS] TryGetSupportedInterfaces MonoTouchFixtures.SystemConfiguration.CaptiveNetworkTest : 22.1934 ms MonoTouchFixtures.SystemConfiguration.NetworkReachabilityTest [PASS] Ctor_Invalid [PASS] CtorIPAddress [PASS] CtorIPAddressPair [PASS] CtorNameAddress [PASS] Schedule [PASS] SetNotification [PASS] SetNotification_GCHandleFreed [PASS] SetNotification_GCHandleFreedWithNull MonoTouchFixtures.SystemConfiguration.NetworkReachabilityTest : 199.5932 ms MonoTouchFixtures.SystemConfiguration.StatusCodeErrorTest [PASS] InvalidStatusCode MonoTouchFixtures.SystemConfiguration.StatusCodeErrorTest : 0.2424 ms MonoTouchFixtures.SystemConfiguration : 222.0729 ms MonoTouchFixtures.UIKit MonoTouchFixtures.UIKit.A_UIStackViewTest [PASS] InitWithFrameTest MonoTouchFixtures.UIKit.A_UIStackViewTest : 0.5837 ms MonoTouchFixtures.UIKit.AccessibilityTest [PASS] ButtonShapesEnabled [PASS] PrefersCrossFadeTransitions [PASS] RequestGuidedAccessSession MonoTouchFixtures.UIKit.AccessibilityTest : 3.7493 ms MonoTouchFixtures.UIKit.ActionSheetTest [PASS] CtorAllNulls [PASS] CtorDefault [PASS] CtorDelegate [PASS] InitWithFrame MonoTouchFixtures.UIKit.ActionSheetTest : 4.2303 ms MonoTouchFixtures.UIKit.ActivityIndicatorViewTest [PASS] InitWithFrame MonoTouchFixtures.UIKit.ActivityIndicatorViewTest : 0.2671 ms MonoTouchFixtures.UIKit.AlertViewDelegateTest [PASS] MyDefault MonoTouchFixtures.UIKit.AlertViewDelegateTest : 0.1149 ms MonoTouchFixtures.UIKit.AlertViewTest [IGNORED] CtorDelegate : UIAlertView is deprecated and unavailable for UIScene based applications. We moved to use UISceneDelegate in Xcode 26. at MonoTouchFixtures.UIKit.AlertViewTest.Setup() [IGNORED] CtorNull : UIAlertView is deprecated and unavailable for UIScene based applications. We moved to use UISceneDelegate in Xcode 26. at MonoTouchFixtures.UIKit.AlertViewTest.Setup() [IGNORED] FirstOtherButtonIndex : UIAlertView is deprecated and unavailable for UIScene based applications. We moved to use UISceneDelegate in Xcode 26. at MonoTouchFixtures.UIKit.AlertViewTest.Setup() [IGNORED] InitWithFrame : UIAlertView is deprecated and unavailable for UIScene based applications. We moved to use UISceneDelegate in Xcode 26. at MonoTouchFixtures.UIKit.AlertViewTest.Setup() MonoTouchFixtures.UIKit.AlertViewTest : 0.3567 ms MonoTouchFixtures.UIKit.AppearanceTest [PASS] Appearance [PASS] Appearance_UITraitCollection [PASS] AppearanceWhenContainedIn [PASS] AppearanceWhenContainedIn_UITraitCollection [PASS] Equality [PASS] Inequality MonoTouchFixtures.UIKit.AppearanceTest : 3.4877 ms MonoTouchFixtures.UIKit.ApplicationTest [PASS] BackgroundTaskInvalid [PASS] BeginBackgroundTask_Null [PASS] MinimumKeepAliveTimeout [PASS] SendAction_Null [PASS] SetKeepAliveTimeout_Null MonoTouchFixtures.UIKit.ApplicationTest : 3.8803 ms MonoTouchFixtures.UIKit.ButtonBarItemTest [PASS] Action_Set [PASS] BackButtonBackgroundImage [PASS] BackgroundImage [PASS] CustomView_Null [PASS] InitWithImage [PASS] InitWithImage2 [PASS] InitWithText [PASS] SetTitleTextAttributes_Null [PASS] TintColor_Null MonoTouchFixtures.UIKit.ButtonBarItemTest : 0.7493 ms MonoTouchFixtures.UIKit.ButtonTest [PASS] InitWithFrame [PASS] NullAllowed [PASS] Tag_12557 MonoTouchFixtures.UIKit.ButtonTest : 0.5463 ms MonoTouchFixtures.UIKit.CellAccessoryTest [PASS] GetPositionAfterAccessory [PASS] GetPositionBeforeAccessory MonoTouchFixtures.UIKit.CellAccessoryTest : 0.9779 ms MonoTouchFixtures.UIKit.CollectionViewControllerTest [PASS] Ctor MonoTouchFixtures.UIKit.CollectionViewControllerTest : 0.2807 ms MonoTouchFixtures.UIKit.CollectionViewTransitionLayoutTest [PASS] Ctor MonoTouchFixtures.UIKit.CollectionViewTransitionLayoutTest : 0.118 ms MonoTouchFixtures.UIKit.ColorTest MonoTouchFixtures.UIKit.ColorTest.ColorExposureCtorTest [IGNORED] ColorExposureCtorTest(0.5d,0.0d,0.0d,0.5d,0.5d,False) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.UIKit.ColorTest.ColorExposureCtorTest(Double red, Double green, Double blue, Double alpha, Double exposure, Boolean isLinearExposure) [IGNORED] ColorExposureCtorTest(0.5d,0.0d,0.0d,0.5d,1.5d,True) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.UIKit.ColorTest.ColorExposureCtorTest(Double red, Double green, Double blue, Double alpha, Double exposure, Boolean isLinearExposure) [IGNORED] ColorExposureCtorTest(0.0d,0.5d,0.0d,0.5d,0.5d,False) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.UIKit.ColorTest.ColorExposureCtorTest(Double red, Double green, Double blue, Double alpha, Double exposure, Boolean isLinearExposure) [IGNORED] ColorExposureCtorTest(0.0d,0.5d,0.0d,0.5d,1.5d,True) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.UIKit.ColorTest.ColorExposureCtorTest(Double red, Double green, Double blue, Double alpha, Double exposure, Boolean isLinearExposure) [IGNORED] ColorExposureCtorTest(0.0d,0.0d,0.5d,0.5d,0.5d,False) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.UIKit.ColorTest.ColorExposureCtorTest(Double red, Double green, Double blue, Double alpha, Double exposure, Boolean isLinearExposure) [IGNORED] ColorExposureCtorTest(0.0d,0.0d,0.5d,0.5d,1.5d,True) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.UIKit.ColorTest.ColorExposureCtorTest(Double red, Double green, Double blue, Double alpha, Double exposure, Boolean isLinearExposure) [IGNORED] ColorExposureCtorTest(0.5d,0.5d,0.5d,0.5d,0.5d,False) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.UIKit.ColorTest.ColorExposureCtorTest(Double red, Double green, Double blue, Double alpha, Double exposure, Boolean isLinearExposure) [IGNORED] ColorExposureCtorTest(0.5d,0.5d,0.5d,0.5d,1.5d,True) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.UIKit.ColorTest.ColorExposureCtorTest(Double red, Double green, Double blue, Double alpha, Double exposure, Boolean isLinearExposure) MonoTouchFixtures.UIKit.ColorTest.ColorExposureCtorTest : 0.5969 ms [PASS] HSB [PASS] HSBA [PASS] HSBA_No_Saturation [PASS] Pattern_7362 [PASS] RGB [PASS] RGBA [PASS] RGBAConstructor [PASS] ToString_ [PASS] UIConfigurationColorTransformerTest MonoTouchFixtures.UIKit.ColorTest.WAConstructor [PASS] WAConstructor(0.2d,0.4d) [PASS] WAConstructor(0.3d,0.5d) [PASS] WAConstructor(0.4d,0.6d) [PASS] WAConstructor(0.5d,0.7d) MonoTouchFixtures.UIKit.ColorTest.WAConstructor : 0.264 ms MonoTouchFixtures.UIKit.ColorTest : 5.221 ms MonoTouchFixtures.UIKit.ControlTest [PASS] AddTargetMakeDirty [PASS] AddTargetTable [PASS] CancelTrackingTest [PASS] InitWithFrame MonoTouchFixtures.UIKit.ControlTest : 13.7643 ms MonoTouchFixtures.UIKit.DatePickerTest [PASS] Defaults [PASS] InitWithFrame [PASS] Locale [PASS] Nulls MonoTouchFixtures.UIKit.DatePickerTest : 19.578 ms MonoTouchFixtures.UIKit.DeviceTest MonoTouchFixtures.UIKit.DeviceTest : 0.001 ms MonoTouchFixtures.UIKit.DictationPhraseTest [PASS] Defaults MonoTouchFixtures.UIKit.DictationPhraseTest : 0.146 ms MonoTouchFixtures.UIKit.DirectionalEdgeInsetsTest [PASS] FromString_Null [PASS] Operators [PASS] ToFromString_Zero MonoTouchFixtures.UIKit.DirectionalEdgeInsetsTest : 0.9991 ms MonoTouchFixtures.UIKit.DocumentTest [PASS] Fields [PASS] PerformAsynchronousFileAccess_Null [PASS] Save MonoTouchFixtures.UIKit.DocumentTest : 8.4512 ms MonoTouchFixtures.UIKit.EdgeInsetsTest [PASS] FromString_Null [PASS] InsetRect [PASS] InsetRect_Zero [PASS] Operators [PASS] ToFromString_Zero MonoTouchFixtures.UIKit.EdgeInsetsTest : 1.1558 ms MonoTouchFixtures.UIKit.FloatRangeTest [IGNORED] Equals : https://github.com/xamarin/maccore/issues/1885 [PASS] IsInfinite [IGNORED] ManagedVersusNative : https://github.com/xamarin/maccore/issues/1885 MonoTouchFixtures.UIKit.FloatRangeTest : 0.3864 ms MonoTouchFixtures.UIKit.FontTest [PASS] GetWeight [PASS] Methods [PASS] NullFonts [PASS] Properties [PASS] TestDescriptors [PASS] WithSize MonoTouchFixtures.UIKit.FontTest : 7.768 ms MonoTouchFixtures.UIKit.GestureRecognizerTest [PASS] DoubleDispose [PASS] GenericCallbackTest [IGNORED] NoStrongCycles : Issue: https://github.com/xamarin/maccore/issues/1345 && WIP PR: https://github.com/dotnet/macios/pull/5462 [PASS] Null MonoTouchFixtures.UIKit.GestureRecognizerTest : 102.3392 ms MonoTouchFixtures.UIKit.GraphicsRendererTest [PASS] BaseDefaultFormat [PASS] ImageDefaultFormat [PASS] PdfDefaultFormat MonoTouchFixtures.UIKit.GraphicsRendererTest : 0.6813 ms MonoTouchFixtures.UIKit.GuidedAccessRestrictionTest [PASS] GetState [PASS] GuidedAccessConfigureAccessibilityFeaturesTest MonoTouchFixtures.UIKit.GuidedAccessRestrictionTest : 102.2621 ms MonoTouchFixtures.UIKit.ImageTest [PASS] CGImageBackend [PASS] CreateAnimatedImage [PASS] FromImage_Null [PASS] TestAutorelease MonoTouchFixtures.UIKit.ImageTest : 75.9271 ms MonoTouchFixtures.UIKit.ImageViewTest [PASS] AnimationImages [PASS] HighlightedAnimationImages_BackingFields [PASS] InitWithFrame MonoTouchFixtures.UIKit.ImageViewTest : 0.708 ms MonoTouchFixtures.UIKit.KeyCommandTest [PASS] Create MonoTouchFixtures.UIKit.KeyCommandTest : 0.2747 ms MonoTouchFixtures.UIKit.LabelTest [PASS] HighlightedTextColor [PASS] InitWithFrame MonoTouchFixtures.UIKit.LabelTest : 1.7237 ms MonoTouchFixtures.UIKit.LayoutConstraintTest [PASS] Create [PASS] FromVisualFormat [PASS] FromVisualFormat_NullMetrics MonoTouchFixtures.UIKit.LayoutConstraintTest : 1.1329 ms MonoTouchFixtures.UIKit.LayoutManagerTest [PASS] Defaults [PASS] GetGlyphsTest MonoTouchFixtures.UIKit.LayoutManagerTest : 2.335 ms MonoTouchFixtures.UIKit.LocalNotificationTest [PASS] DefaultValues [PASS] NullValues MonoTouchFixtures.UIKit.LocalNotificationTest : 0.7936 ms MonoTouchFixtures.UIKit.NavigationBarTest [PASS] BackgroundImage [PASS] InitWithFrame MonoTouchFixtures.UIKit.NavigationBarTest : 1.1017 ms MonoTouchFixtures.UIKit.NavigationControllerTest [PASS] ViewControllers_EmptyNull MonoTouchFixtures.UIKit.NavigationControllerTest : 2.5008 ms MonoTouchFixtures.UIKit.NavigationItemTest MonoTouchFixtures.UIKit.NavigationItemTest : 0.0011 ms MonoTouchFixtures.UIKit.NibTest [PASS] FromName_DoesNotExists MonoTouchFixtures.UIKit.NibTest : 0.2024 ms MonoTouchFixtures.UIKit.NSDiffableDataSourceSnapshotTest [PASS] GHIssue6567Test [PASS] ObjectUsageTest MonoTouchFixtures.UIKit.NSDiffableDataSourceSnapshotTest : 0.4927 ms MonoTouchFixtures.UIKit.PageControlTest [PASS] InitWithFrame MonoTouchFixtures.UIKit.PageControlTest : 0.8697 ms MonoTouchFixtures.UIKit.PageViewControllerTest [PASS] Defaults [PASS] SetViewControllers MonoTouchFixtures.UIKit.PageViewControllerTest : 1.1752 ms MonoTouchFixtures.UIKit.PanGestureRecognizerTest [PASS] Null MonoTouchFixtures.UIKit.PanGestureRecognizerTest : 0.1779 ms MonoTouchFixtures.UIKit.PasteboardTest [PASS] ImagesTest MonoTouchFixtures.UIKit.PasteboardTest : 79.0468 ms MonoTouchFixtures.UIKit.PickerViewTest [PASS] ConformsTo [PASS] InitWithFrame MonoTouchFixtures.UIKit.PickerViewTest : 0.7558 ms MonoTouchFixtures.UIKit.PopoverBackgroundViewTest [PASS] InitWithFrame MonoTouchFixtures.UIKit.PopoverBackgroundViewTest : 0.1664 ms MonoTouchFixtures.UIKit.PopoverControllerTest [PASS] Defaults [PASS] PopoverBackgroundViewType [PASS] PresentFromBarButtonItem_BadButton [PASS] PresentFromRect [PASS] PresentFromRect_BadView MonoTouchFixtures.UIKit.PopoverControllerTest : 2.2837 ms MonoTouchFixtures.UIKit.PopoverPresentationControllerTest [PASS] PopoverBackgroundViewType MonoTouchFixtures.UIKit.PopoverPresentationControllerTest : 0.4369 ms MonoTouchFixtures.UIKit.ProgressViewTest [PASS] InitWithFrame MonoTouchFixtures.UIKit.ProgressViewTest : 1.2129 ms MonoTouchFixtures.UIKit.ReferenceLibraryViewControllerTest [IGNORED] DictionaryHasDefinitionForTerm : ios6 beta issues [IGNORED] InitWithTerm : https://github.com/xamarin/maccore/issues/2348 MonoTouchFixtures.UIKit.ReferenceLibraryViewControllerTest : 0.0459 ms MonoTouchFixtures.UIKit.ScrollViewTest [PASS] InitWithFrame MonoTouchFixtures.UIKit.ScrollViewTest : 0.3377 ms MonoTouchFixtures.UIKit.SearchBarTest [PASS] InitWithFrame [PASS] TextInput MonoTouchFixtures.UIKit.SearchBarTest : 0.8658 ms MonoTouchFixtures.UIKit.SegmentedControlTest [PASS] Appearance_7 [PASS] BackgroundImage [PASS] CtorNSArray [PASS] CtorNSString [PASS] CtorObjects [PASS] CtorString [PASS] CtorUIImage [PASS] InitWithFrame [PASS] TitleTextAttributes MonoTouchFixtures.UIKit.SegmentedControlTest : 9.6463 ms MonoTouchFixtures.UIKit.SimpleTextPrintFormatterTest [PASS] StringCtor MonoTouchFixtures.UIKit.SimpleTextPrintFormatterTest : 2.7021 ms MonoTouchFixtures.UIKit.SplitViewControllerTest [PASS] Defaults [PASS] PresentsWithGesture MonoTouchFixtures.UIKit.SplitViewControllerTest : 0.9493 ms MonoTouchFixtures.UIKit.StepperTest [PASS] BackgroundImage [PASS] InitWithFrame MonoTouchFixtures.UIKit.StepperTest : 1.8815 ms MonoTouchFixtures.UIKit.StringAttributesTest [PASS] MutableStringAttributesTest [PASS] PrematureDisposal_BarItem [PASS] PrematureDisposal_SearchBar [PASS] PrematureDisposal_SegmentedControl [PASS] RetainCount [PASS] RetainCount_7 MonoTouchFixtures.UIKit.StringAttributesTest : 10.526 ms MonoTouchFixtures.UIKit.SwitchTest [PASS] InitWithFrame MonoTouchFixtures.UIKit.SwitchTest : 1.455 ms MonoTouchFixtures.UIKit.TabBarControllerTest [PASS] Ctor_Defaults MonoTouchFixtures.UIKit.TabBarControllerTest : 3.0629 ms MonoTouchFixtures.UIKit.TabBarItemTest [PASS] Ctor_2 [PASS] Ctor_3 [PASS] Ctor_3a_Null [PASS] Ctor_3b_Null [PASS] Ctor_Defaults [PASS] SelectedImage_7a [PASS] SelectedImage_7b MonoTouchFixtures.UIKit.TabBarItemTest : 1.5175 ms MonoTouchFixtures.UIKit.TabBarTest [PASS] BackgroundImage [PASS] Customizing [PASS] InitWithFrame [PASS] Items [PASS] SelectedImageTintColor [PASS] SelectedItem [PASS] SelectionIndicatorImage [PASS] TintColor MonoTouchFixtures.UIKit.TabBarTest : 3.6698 ms MonoTouchFixtures.UIKit.TableViewCellTest [PASS] InitWithFrame MonoTouchFixtures.UIKit.TableViewCellTest : 0.231 ms MonoTouchFixtures.UIKit.TableViewControllerTest [PASS] RefreshControl_18744 MonoTouchFixtures.UIKit.TableViewControllerTest : 1.1869 ms MonoTouchFixtures.UIKit.TableViewTest [PASS] InitWithFrame MonoTouchFixtures.UIKit.TableViewTest : 0.2907 ms MonoTouchFixtures.UIKit.TextAttachmentTest [PASS] CtorNull MonoTouchFixtures.UIKit.TextAttachmentTest : 0.1166 ms MonoTouchFixtures.UIKit.TextContainerTest [PASS] Layout MonoTouchFixtures.UIKit.TextContainerTest : 0.0944 ms MonoTouchFixtures.UIKit.TextFieldTest [PASS] EmptySelection [PASS] GetCaretRectForPositiont_Null [PASS] InitWithFrame [PASS] InputAccessoryViewTest [PASS] LeftRight [PASS] TextInputTraits MonoTouchFixtures.UIKit.TextFieldTest : 2.5951 ms MonoTouchFixtures.UIKit.TextViewTest [PASS] EmptySelection [PASS] EmptySelection2 [PASS] InitWithFrame [PASS] LayoutManager [PASS] NonEmptySelection [PASS] TextInputTraits MonoTouchFixtures.UIKit.TextViewTest : 4.94 ms MonoTouchFixtures.UIKit.ToolbarTest [PASS] BackgroundImage [PASS] InitWithFrame MonoTouchFixtures.UIKit.ToolbarTest : 0.444 ms MonoTouchFixtures.UIKit.UIAlertControllerTest [PASS] InitWithNibNameTest MonoTouchFixtures.UIKit.UIAlertControllerTest : 0.798 ms MonoTouchFixtures.UIKit.UIContentSizeCategoryTest [PASS] Compare [PASS] GetPreferredContentSizeCategoryTest [PASS] IsAccessibilityCategory MonoTouchFixtures.UIKit.UIContentSizeCategoryTest : 2.9488 ms MonoTouchFixtures.UIKit.UIDragDropSessionExtensionsTest [PASS] LoadObjectsTest MonoTouchFixtures.UIKit.UIDragDropSessionExtensionsTest : 0.2294 ms MonoTouchFixtures.UIKit.UIListSeparatorConfigurationTest [PASS] AutomaticInsetsTest MonoTouchFixtures.UIKit.UIListSeparatorConfigurationTest : 0.5319 ms MonoTouchFixtures.UIKit.UIPasteConfigurationSupportingTest [PASS] SKNodeTest [PASS] UIViewControllerPasteTest [PASS] UIViewPasteTest MonoTouchFixtures.UIKit.UIPasteConfigurationSupportingTest : 0.518 ms MonoTouchFixtures.UIKit.UIPointerAccessoryTest [PASS] UIPointerAccessoryPositionBottomLeftTest [PASS] UIPointerAccessoryPositionBottomRightTest [PASS] UIPointerAccessoryPositionBottomTest [PASS] UIPointerAccessoryPositionLeftTest [PASS] UIPointerAccessoryPositionRightTest [PASS] UIPointerAccessoryPositionTopLeftTest [PASS] UIPointerAccessoryPositionTopRightTest [PASS] UIPointerAccessoryPositionTopTest MonoTouchFixtures.UIKit.UIPointerAccessoryTest : 8.8485 ms MonoTouchFixtures.UIKit.UISearchControllerTest [PASS] InitWithNibNameTest MonoTouchFixtures.UIKit.UISearchControllerTest : 1.6105 ms MonoTouchFixtures.UIKit.UISpringLoadedInteractionSupportingTest [PASS] UIAlertControllerSpringLoadTest [PASS] UIBarButtonItemSpringLoadTest [PASS] UIButtonSpringLoadTest [PASS] UICollectionViewSpringLoadTest [PASS] UISearchTabSpringLoadTest [PASS] UISegmentedControlSpringLoadTest [PASS] UITabBarItemSpringLoadTest [PASS] UITabBarSpringLoadTest [PASS] UITabGroupSpringLoadTest [PASS] UITableViewSpringLoadTest [PASS] UITabSpringLoadTest MonoTouchFixtures.UIKit.UISpringLoadedInteractionSupportingTest : 1.798 ms MonoTouchFixtures.UIKit.UITraitOverridesTest [PASS] InlinedInUIPresentationController [PASS] InlinedInUIView [PASS] InlinedInUIViewController [PASS] InlinedInUIWindowScene [PASS] RegisterForTraitChanges_ClassArray [PASS] RegisterForTraitChanges_Generic [PASS] RegisterForTraitChanges_ParamsTypeArray [PASS] RegisterForTraitChanges_TypeArray MonoTouchFixtures.UIKit.UITraitOverridesTest : 5.4448 ms MonoTouchFixtures.UIKit.ViewControllerTest [PASS] AppearanceTransition [PASS] Bug3189 [PASS] Bug3489 [PASS] Defaults [PASS] NonModal [PASS] NSAction_Null [PASS] Toolbars_Null [PASS] View_Null MonoTouchFixtures.UIKit.ViewControllerTest : 4.1921 ms MonoTouchFixtures.UIKit.ViewTest [PASS] Animate_Null_a1 [PASS] Animate_Null_a2 [PASS] Animate_Null_a3 [PASS] Animate_Null_c1 [PASS] Animate_Null_c2 [PASS] AnimateNotify_Null_a1 [PASS] AnimateNotify_Null_a2 [PASS] AnimateNotify_Null_c1 [PASS] AnimateNotify_Null_c2 [PASS] BackgroundColorTest [PASS] Convert_Null [PASS] Equality [PASS] HitTest_Null [PASS] InitWithFrame [PASS] PointInside_Null [PASS] SizeThatFits [PASS] Subviews [PASS] TintColor [PASS] TraitMatch [PASS] TraitTest [PASS] Transition_Null_a1 [PASS] Transition_Null_c1 [PASS] Transition_Null_c2 [PASS] TransitionNotify_Null_a1 [PASS] TransitionNotify_Null_c1 [PASS] TransitionNotify_Null_c2 MonoTouchFixtures.UIKit.ViewTest : 6.918 ms MonoTouchFixtures.UIKit.WindowTest [PASS] Convert_Null [PASS] InitWithFrame [PASS] IsKeyWindow_5199 [PASS] Level MonoTouchFixtures.UIKit.WindowTest : 2.1831 ms MonoTouchFixtures.UIKit : 538.5425 ms MonoTouchFixtures.UniformTypeIdentifiers MonoTouchFixtures.UniformTypeIdentifiers.UTTypeTests [PASS] Archive MonoTouchFixtures.UniformTypeIdentifiers.UTTypeTests : 0.3892 ms MonoTouchFixtures.UniformTypeIdentifiers : 0.3993 ms MonoTouchFixtures.UserNotifications MonoTouchFixtures.UserNotifications.UNNotificationInterruptionLevelTest [PASS] EnumTest MonoTouchFixtures.UserNotifications.UNNotificationInterruptionLevelTest : 0.0385 ms MonoTouchFixtures.UserNotifications : 0.0415 ms MonoTouchFixtures.VerifyAllTestsArePreserved [PASS] Test MonoTouchFixtures.VerifyAllTestsArePreserved : 291.9632 ms MonoTouchFixtures.VideoToolbox MonoTouchFixtures.VideoToolbox.VTCompressionPropertyCameraCalibrationTest [IGNORED] DefaultValues : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTCompressionPropertyCameraCalibrationTest.DefaultValues() MonoTouchFixtures.VideoToolbox.VTCompressionPropertyCameraCalibrationTest : 0.3142 ms MonoTouchFixtures.VideoToolbox.VTCompressionSessionTests [PASS] CompressionSessionCreateTest [IGNORED] CompressionSessionGetSerializablePropertiesTest : Crashes with SIGSEGV when trying to dispose session after calling session.GetSerializableProperties () [PASS] CompressionSessionGetSupportedPropertiesTest [PASS] CompressionSessionPropertiesTest [PASS] CompressionSessionSetCompressionPropertiesMultiPassStorageTest [PASS] CompressionSessionSetCompressionPropertiesTest [PASS] CompressionSessionSetPropertiesTest MonoTouchFixtures.VideoToolbox.VTCompressionSessionTests.TestCallback [PASS] TestCallback(True) [PASS] TestCallback(False) MonoTouchFixtures.VideoToolbox.VTCompressionSessionTests.TestCallback : 111.3769 ms MonoTouchFixtures.VideoToolbox.VTCompressionSessionTests.TestMultiImage [IGNORED] TestMultiImage(True,True) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTCompressionSessionTests.TestMultiImage(Boolean stronglyTyped, Boolean customCallback) [IGNORED] TestMultiImage(False,True) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTCompressionSessionTests.TestMultiImage(Boolean stronglyTyped, Boolean customCallback) [IGNORED] TestMultiImage(True,False) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTCompressionSessionTests.TestMultiImage(Boolean stronglyTyped, Boolean customCallback) [IGNORED] TestMultiImage(False,False) : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTCompressionSessionTests.TestMultiImage(Boolean stronglyTyped, Boolean customCallback) MonoTouchFixtures.VideoToolbox.VTCompressionSessionTests.TestMultiImage : 0.616 ms MonoTouchFixtures.VideoToolbox.VTCompressionSessionTests : 197.5162 ms MonoTouchFixtures.VideoToolbox.VTDecompressionSessionTests [PASS] DecodeFrameCallbackTest [IGNORED] DecodeFrameMultiImageCallbackTest : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTDecompressionSessionTests.DecodeFrameMultiImageCallbackTest() [IGNORED] DecodeFrameSetMultiImageCallbackTest : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTDecompressionSessionTests.DecodeFrameSetMultiImageCallbackTest() [IGNORED] DecodeFrameTest : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTDecompressionSessionTests.DecodeFrameTest() [PASS] DecompressionSessionCreateTest [PASS] DecompressionSessionGetSupportedPropertiesTest [PASS] DecompressionSessionPropertiesTest [PASS] DecompressionSessionSetDecompressionPropertiesTest [PASS] DecompressionSessionSetPropertiesTest MonoTouchFixtures.VideoToolbox.VTDecompressionSessionTests : 19.3335 ms MonoTouchFixtures.VideoToolbox.VTFrameSiloTests [PASS] ForEachTest [PASS] FrameSiloCreateTest [PASS] SetTimeRangesTest MonoTouchFixtures.VideoToolbox.VTFrameSiloTests : 2.3281 ms MonoTouchFixtures.VideoToolbox.VTHdrPerFrameMetadataGenerationSessionTest [PASS] AttachMetadataTest [PASS] Create_NSDictionary_Test [PASS] Create_VTHdrPerFrameMetadataGenerationOptions_Test [PASS] GetTypeId MonoTouchFixtures.VideoToolbox.VTHdrPerFrameMetadataGenerationSessionTest : 0.3446 ms MonoTouchFixtures.VideoToolbox.VTLowLatencyFrameInterpolationConfigurationTest [IGNORED] NumberOfInterpolatedFramesCtor : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTLowLatencyFrameInterpolationConfigurationTest.NumberOfInterpolatedFramesCtor() [IGNORED] WithSpatialScaleFactorCtor : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTLowLatencyFrameInterpolationConfigurationTest.WithSpatialScaleFactorCtor() MonoTouchFixtures.VideoToolbox.VTLowLatencyFrameInterpolationConfigurationTest : 0.2084 ms MonoTouchFixtures.VideoToolbox.VTMotionEstimationSessionTest [IGNORED] CreateStronglyTypedTest : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTMotionEstimationSessionTest.CreateStronglyTypedTest() [IGNORED] CreateTest : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTMotionEstimationSessionTest.CreateTest() [IGNORED] GetTypeId : Requires the platform version shipped with Xcode 26.0 at TestRuntime.AssertXcodeVersion(Int32 major, Int32 minor, Int32 build) at MonoTouchFixtures.VideoToolbox.VTMotionEstimationSessionTest.GetTypeId() MonoTouchFixtures.VideoToolbox.VTMotionEstimationSessionTest : 0.2347 ms MonoTouchFixtures.VideoToolbox.VTMultiPassStorageTests [PASS] MultiPassStorageCloseTest [PASS] MultiPassStorageCreateTest MonoTouchFixtures.VideoToolbox.VTMultiPassStorageTests : 1.045 ms MonoTouchFixtures.VideoToolbox.VTPixelRotationSessionTests [PASS] CreateTest [PASS] RotateImageTest [PASS] SetRotationPropertiesTest MonoTouchFixtures.VideoToolbox.VTPixelRotationSessionTests : 1.0755 ms MonoTouchFixtures.VideoToolbox.VTPixelTransferSessionTests [PASS] PixelTransferSessionCreateTest [PASS] PixelTransferSessionTransferImageTest [PASS] SetTransferPropertiesTest MonoTouchFixtures.VideoToolbox.VTPixelTransferSessionTests : 1.4838 ms MonoTouchFixtures.VideoToolbox.VTUtilitiesTests [PASS] ToCGImageTest MonoTouchFixtures.VideoToolbox.VTUtilitiesTests : 2.6922 ms MonoTouchFixtures.VideoToolbox.VTVideoEncoderListTests [PASS] SupportedEncoderPropertiesTest [PASS] VideoEncoderListTest MonoTouchFixtures.VideoToolbox.VTVideoEncoderListTests : 4.3176 ms MonoTouchFixtures.VideoToolbox : 230.9439 ms MonoTouchFixtures.Vision MonoTouchFixtures.Vision.VNCircleTests [PASS] CreateUsingDiameterCtorTest [PASS] CreateUsingDiameterTest [PASS] CreateUsingInvalidOptionCtorTest [PASS] CreateUsingRadiusCtorTest [PASS] CreateUsingRadiusTest MonoTouchFixtures.Vision.VNCircleTests : 0.677 ms MonoTouchFixtures.Vision.VNGeometryUtilsTests [PASS] CreateBoundingCircleTest MonoTouchFixtures.Vision.VNGeometryUtilsTests : 0.2589 ms MonoTouchFixtures.Vision.VNGetCameraRelativePositionTest [PASS] GetCameraRelativePositionTest MonoTouchFixtures.Vision.VNGetCameraRelativePositionTest : 181.8943 ms MonoTouchFixtures.Vision.VNRequestTests [PASS] VNSupportedRevisionsTest [PASS] VNSupportedRevisionsTwoTest [PASS] VNSupportedRevisionsUnsupportedTest MonoTouchFixtures.Vision.VNRequestTests : 14.4969 ms MonoTouchFixtures.Vision.VNUtilsTests [PASS] GetImagePointTest [PASS] GetImageRectTest [PASS] GetNormalizedFaceBoundingBoxPointTest [PASS] GetNormalizedPointTest [PASS] GetNormalizedRectTest [PASS] IsIdentityTest MonoTouchFixtures.Vision.VNUtilsTests : 0.4432 ms MonoTouchFixtures.Vision.VNVectorTests [PASS] VNVectorCreateTest [PASS] VNVectorCtorTest MonoTouchFixtures.Vision.VNVectorTests : 0.3004 ms MonoTouchFixtures.Vision : 198.1059 ms MonoTouchFixtures.WebKit MonoTouchFixtures.WebKit.NSAttributedStringCatagoryTest [PASS] LoadHtmlAsync_NSUrl MonoTouchFixtures.WebKit.NSAttributedStringCatagoryTest : 218.3197 ms MonoTouchFixtures.WebKit.WKPreferencesTest [PASS] TextInteractionEnabledTest MonoTouchFixtures.WebKit.WKPreferencesTest : 0.4515 ms MonoTouchFixtures.WebKit.WKWebExtensionControllerTest [PASS] AllExtensionDataTypes [PASS] DataRecords MonoTouchFixtures.WebKit.WKWebExtensionControllerTest : 0.9391 ms MonoTouchFixtures.WebKit : 219.728 ms MonoTouchFixtures.XKit MonoTouchFixtures.XKit.NSTextStorageTest [PASS] InitWithString MonoTouchFixtures.XKit.NSTextStorageTest : 0.2718 ms MonoTouchFixtures.XKit : 0.2768 ms MonoTouchFixtures : 55924.1983 ms monotouchtest monotouchtest.AUParameterNodeTest [PASS] CreateTokenByAddingParameterRecordingObserver [IGNORED] ImplementorDisplayNameWithLengthCallback : ImplementorDisplayNameWithLengthCallback never invoked when user requests truncated version of node's name. [PASS] ImplementorStringFromValueCallback [PASS] ImplementorValueFromStringCallback [PASS] RemoveParameterObserver monotouchtest.AUParameterNodeTest : 1052.9752 ms monotouchtest.AVAssetDownloadUrlSessionTests [PASS] AVAssetDownloadUrlSessionStaticNotSupported [PASS] CreateSessionTest monotouchtest.AVAssetDownloadUrlSessionTests : 14.7105 ms monotouchtest.AVCapturePhotoBracketSettingsTest [PASS] TestConstructor monotouchtest.AVCapturePhotoBracketSettingsTest : 1112.9061 ms monotouchtest.AVFoundation monotouchtest.AVFoundation.AVVideoSettingsCompressedTest monotouchtest.AVFoundation.AVVideoSettingsCompressedTest : 0.001 ms monotouchtest.AVFoundation : 0.0077 ms monotouchtest.CoreGraphics monotouchtest.CoreGraphics.CGImagePropertiesExifTest [PASS] ConstructorAndBasicPropertiesTest [PASS] ConstructorWithDictionaryTest [PASS] ExposureProgramTest [PASS] FloatingPointPropertiesTest [PASS] IntegrationWithCGImagePropertiesTest [PASS] ISOSpeedRatingsTest [PASS] NullablePropertiesTest monotouchtest.CoreGraphics.CGImagePropertiesExifTest : 3.1733 ms monotouchtest.CoreGraphics.CGImagePropertiesGPSTest [PASS] AltitudeTest [PASS] ConstructorAndBasicPropertiesTest [PASS] ConstructorWithDictionaryTest [PASS] EdgeCaseCoordinatesTest [PASS] IntegrationWithCGImagePropertiesTest [PASS] LongitudeRefAndLatitudeRefTest [PASS] NegativeCoordinatesTest [PASS] NullablePropertiesTest monotouchtest.CoreGraphics.CGImagePropertiesGPSTest : 5.4797 ms monotouchtest.CoreGraphics.CGImagePropertiesIptcTest [PASS] ConstructorAndBasicPropertiesTest [PASS] ConstructorWithDictionaryTest [PASS] CopyrightAndCreditPropertiesTest [PASS] EmptyStringPropertiesTest [PASS] IntegrationWithCGImagePropertiesTest [PASS] LocationPropertiesTest [PASS] NullablePropertiesTest monotouchtest.CoreGraphics.CGImagePropertiesIptcTest : 1.5953 ms monotouchtest.CoreGraphics.CGImagePropertiesJfifTest [PASS] ConstructorAndBasicPropertiesTest [PASS] ConstructorWithDictionaryTest [PASS] DensityValuesTest [PASS] IntegrationWithCGImagePropertiesTest [PASS] LargeDensityValuesTest [PASS] NullablePropertiesTest [PASS] ZeroDensityTest monotouchtest.CoreGraphics.CGImagePropertiesJfifTest : 1.8194 ms monotouchtest.CoreGraphics.CGImagePropertiesPngTest [PASS] ConstructorAndBasicPropertiesTest [PASS] ConstructorWithDictionaryTest [PASS] EmptyStringPropertiesTest [PASS] GammaValuesTest [PASS] IntegrationWithCGImagePropertiesTest [PASS] NullablePropertiesTest [PASS] NumericPropertiesTest [PASS] PixelsPerMeterTest [PASS] UnicodeStringPropertiesTest monotouchtest.CoreGraphics.CGImagePropertiesPngTest : 1.8419 ms monotouchtest.CoreGraphics.CGImagePropertiesTiffTest [PASS] ConstructorAndBasicPropertiesTest [PASS] ConstructorWithDictionaryTest [PASS] EmptyStringTest [PASS] IntegrationWithCGImagePropertiesTest [PASS] LargeResolutionValuesTest [PASS] NullablePropertiesTest [PASS] OrientationTest [PASS] ResolutionValuesTest [PASS] SoftwarePropertyTest [PASS] UnicodeStringTest [PASS] ZeroResolutionTest monotouchtest.CoreGraphics.CGImagePropertiesTiffTest : 2.2183 ms monotouchtest.CoreGraphics : 16.1625 ms monotouchtest.Network monotouchtest.Network.NWPathMonitorTest [PASS] PathIsAlwaysUpdatedWithNewHandlerTest [PASS] ProhibitInterfaceTypeTest [PASS] StatusPropertyTest monotouchtest.Network.NWPathMonitorTest : 304.6321 ms monotouchtest.Network : 304.638 ms monotouchtest.NSLinguisticAnalysisTest [PASS] EnumerateSubstringsInRangeTest [PASS] GetLinguisticTagsTest [PASS] StopEnumerateSubstringsInRangeTest monotouchtest.NSLinguisticAnalysisTest : 120.88 ms monotouchtest.NSMutableDictionaryTest [PASS] AddEntries [PASS] Bug39993 [PASS] IndexerTest monotouchtest.NSMutableDictionaryTest : 0.5805 ms monotouchtest.NSStringTest [PASS] LocalizedFormatTest monotouchtest.NSStringTest.NSStringSubstringExceptions [PASS] NSStringSubstringExceptions("asdf",-1,0,"start") [PASS] NSStringSubstringExceptions("asdf",0,-1,"length") [PASS] NSStringSubstringExceptions("asdf",5,0,"start") [PASS] NSStringSubstringExceptions("asdf",0,5,"length") monotouchtest.NSStringTest.NSStringSubstringExceptions : 0.2032 ms monotouchtest.NSStringTest.TestFromHandle_owns [PASS] TestFromHandle_owns(False) [PASS] TestFromHandle_owns(True) monotouchtest.NSStringTest.TestFromHandle_owns : 0.3074 ms monotouchtest.NSStringTest.TestNSStringSubstrings [PASS] TestNSStringSubstrings("asdf",0,4) [PASS] TestNSStringSubstrings("asdf",0,2) [PASS] TestNSStringSubstrings("asdf",1,3) [PASS] TestNSStringSubstrings("asdf",4,0) monotouchtest.NSStringTest.TestNSStringSubstrings : 0.3208 ms monotouchtest.NSStringTest : 2.1387 ms monotouchtest : 2625.0453 ms MonoTouchTest MonoTouchTest.Network MonoTouchTest.Network.NWPrivacyContextTest [PASS] AddProxyTest [PASS] ClearProxyTest [PASS] DisableLoggingTest [PASS] FlushCacheTest [PASS] RequireEncryptedNameResolutionTest MonoTouchTest.Network.NWPrivacyContextTest : 0.443 ms MonoTouchTest.Network : 0.4457 ms MonoTouchTest : 0.4489 ms XamarinTests XamarinTests.ObjCRuntime XamarinTests.ObjCRuntime.RegistrarSharedTest [PASS] IntPtrCtor XamarinTests.ObjCRuntime.RegistrarSharedTest : 0.6091 ms XamarinTests.ObjCRuntime : 0.6128 ms XamarinTests : 0.6161 ms monotouchtest : 360356.4512 ms EmbeddedResources EmbeddedResources EmbeddedResources.ResourcesTest [PASS] Embedded EmbeddedResources.ResourcesTest : 3.7957 ms EmbeddedResources : 3.7989 ms EmbeddedResources : 3.8219 ms bindings-test Xamarin Xamarin.BindingTests Xamarin.BindingTests.ProtocolTest [PASS] Constructors [PASS] OnlyProtocol [PASS] ProtocolMembers [PASS] ProtocolWithBaseType [PASS] ProtocolWithBaseTypeAndModel Xamarin.BindingTests.ProtocolTest : 2.0124 ms Xamarin.BindingTests.RegistrarBindingTest [PASS] BlockCallback [PASS] DerivedClassBlockCallback OptionalCallback OptionalStaticCallback Xamarin.BindingTests.RegistrarBindingTest.LinkedAway [IGNORED] LinkedAway(True,True) : This test is only applicable if optimized & linking all assemblies. at Xamarin.BindingTests.RegistrarBindingTest.LinkedAway(Boolean required, Boolean instance) [IGNORED] LinkedAway(True,False) : This test is only applicable if optimized & linking all assemblies. at Xamarin.BindingTests.RegistrarBindingTest.LinkedAway(Boolean required, Boolean instance) [IGNORED] LinkedAway(False,True) : This test is only applicable if optimized & linking all assemblies. at Xamarin.BindingTests.RegistrarBindingTest.LinkedAway(Boolean required, Boolean instance) [IGNORED] LinkedAway(False,False) : This test is only applicable if optimized & linking all assemblies. at Xamarin.BindingTests.RegistrarBindingTest.LinkedAway(Boolean required, Boolean instance) Xamarin.BindingTests.RegistrarBindingTest.LinkedAway : 0.4429 ms Xamarin.BindingTests.RegistrarBindingTest.ProtocolWithBlockProperties [PASS] ProtocolWithBlockProperties(True,True) [PASS] ProtocolWithBlockProperties(True,False) [PASS] ProtocolWithBlockProperties(False,True) [PASS] ProtocolWithBlockProperties(False,False) Xamarin.BindingTests.RegistrarBindingTest.ProtocolWithBlockProperties : 0.2887 ms Xamarin.BindingTests.RegistrarBindingTest.ProtocolWithNativeBlockProperties [PASS] ProtocolWithNativeBlockProperties(True,True) [PASS] ProtocolWithNativeBlockProperties(True,False) [PASS] ProtocolWithNativeBlockProperties(False,True) [PASS] ProtocolWithNativeBlockProperties(False,False) Xamarin.BindingTests.RegistrarBindingTest.ProtocolWithNativeBlockProperties : 1.5279 ms Xamarin.BindingTests.RegistrarBindingTest.ProtocolWithReturnValues [PASS] ProtocolWithReturnValues(True,True) [PASS] ProtocolWithReturnValues(True,False) [PASS] ProtocolWithReturnValues(False,True) [PASS] ProtocolWithReturnValues(False,False) Xamarin.BindingTests.RegistrarBindingTest.ProtocolWithReturnValues : 0.2967 ms Xamarin.BindingTests.RegistrarBindingTest : 2.9366 ms Xamarin.BindingTests : 4.9541 ms Xamarin.Tests Xamarin.Tests.RuntimeTest [PASS] GlobalStringTest [PASS] MainThreadDeallocationTest [PASS] MainThreadDeallocationTestQOS [PASS] SwiftTest [PASS] SwiftTestClass2 [PASS] SwiftTypeEncodings [PASS] VeryGeneric [PASS] WrapperTypeLookupTest Xamarin.Tests.RuntimeTest : 204.3756 ms Xamarin.Tests : 204.388 ms Xamarin : 209.3491 ms bindings-test : 209.4002 ms Tests run: 3248 Passed: 3234 Inconclusive: 9 Failed: 5 Ignored: 188 ]]> ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/devices.xml ================================================  /Applications/Xcode113.app/Contents/Developer Activated 5650776064 100 b0:19:c6:94:f2:26 17D50 32789 arm64 Development iPhone 1 1 5696DC36EE3249859D465DA37E6EA912 Standard D211AP Usb 359401080217877 True True True MQ8L2 iPhone True 1 iPhone10,5 13.3.1 C39V7FLJJCM2 58561933312 D9DCBED1EC414ECE9A2353364C2AC454 1434502442682426 True b0:19:c6:94:f2:25 iPhone E383EA784E604083B5B602853A364759 Usb False True iPhone 6D6C141F40CC4A4DB7967FCCE974ECE3 iPhone D384F1159D5748C2A590420D64FA8CBE Usb False True XQAiPhone5Sa 245481CA878E4AC0825DFBFDBCFD16BB iPod 5D61F84BC2804AA698C2DFC1B7FDD200 Usb False True XQAiPodTouch5f 1CBD36A8F42441A78D57443EFE0BD666 Activated 43999293440 100 88:64:40:5c:ee:c5 17A577 32816 arm64e Development iPhone 1 1 8A450AA31EA94191AD6B02455F377CC1 Standard False N104AP Usb 353981100627011 True True True MWL72 Manuel’s iPhone True 1 iPhone12,1 13.0 DNPZ893NN72J 54814302208 58F21118E4D34FD69EAB7860BB9B38A0 2852055891869742 True 88:64:40:67:a1:06 Watch 9608C09A197C4A6EB41B454D91976654 3 False True Manuel’s Apple Watch BC87FE47DA2A421CA791AD667E7E57E8 bd3fd054136767445ff47819f65b74867a48e591 Activated 22636388352 90 a4:d1:d2:74:24:89 13G36 35136 armv7f iPad white unknown E854B2C3E7C8451BAF8053EC4DAAEE49 Standard K93AP Usb True True True MC980 Manuel de la Pena Saenz’s iPad False 1 iPad2,1 9.3.5 DN6FV2LSDKPJ 29631791104 51F3354D448D4814825D07DC5658C19B 4130031754541 False a4:d1:d2:74:24:88 ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/run-log.txt ================================================ 14:48:45.0272010 Test log server listening on: 0.0.0.0:49987 14:48:45.0308310 System log for the 'iPhone X (iOS 13.3) - created by xharness' simulator is: /Users/mandel/Library/Logs/CoreSimulator/96EBF3D8-393E-4166-B1AF-4A3BDEAC5021/system.log 14:48:45.0314300 *** Executing com.xamarin.bcltests.mscorlib Part 1/Classic in the simulator *** 14:48:45.0328640 Starting test run 14:48:45.0332180 /Users/mandel/Xamarin/xamarin-macios/master/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/Current/bin/mlaunch --sdkroot /Applications/Xcode113.app -v -argument=-connection-mode -argument=none -argument=-app-arg:-autostart -setenv=NUNIT_AUTOSTART=true -argument=-app-arg:-autoexit -setenv=NUNIT_AUTOEXIT=true -argument=-app-arg:-enablenetwork -setenv=NUNIT_ENABLE_NETWORK=true -argument=-app-arg:-hostname:127.0.0.1 -setenv=NUNIT_HOSTNAME=127.0.0.1 -argument=-app-arg:-transport:Tcp -setenv=NUNIT_TRANSPORT=TCP -argument=-app-arg:-hostport:49987 -setenv=NUNIT_HOSTPORT=49987 --launchsim "/Users/mandel/Xamarin/xamarin-macios/master/xamarin-macios/tests/xharness/tmp-test-dir/mscorlib Part 1/bin/mscorlib Part 1/iPhoneSimulator/Debug-unified/com.xamarin.bcltests.mscorlib Part 1.app" --stdout=/dev/ttys000 --stderr=/dev/ttys000 --device=:v2:udid=96EBF3D8-393E-4166-B1AF-4A3BDEAC5021 14:48:45.2834160 Using Xcode 11.3 found in /Applications/Xcode113.app 14:48:45.2865790 Xamarin.Hosting: Xamarin.Hosting 14:48:45.2867370 Xamarin.Hosting: Version: cec1cba137 (master) 14:48:45.2867590 Xamarin.Hosting: Xcode: /Applications/Xcode113.app 14:48:45.2886960 Xamarin.Hosting: Xcode Version: 11.3 14:48:45.2892130 Xamarin.Hosting: Verbosity: 11 14:48:45.2987220 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/DVTFoundation 14:48:45.3024060 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DVTiPhoneSimulatorRemoteClient.framework/DVTiPhoneSimulatorRemoteClient 14:48:45.3024840 Xamarin.Hosting: Loaded /Library/Developer/PrivateFrameworks/CoreSimulator.framework/CoreSimulator 14:48:45.3026130 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DTDeviceKitBase.framework/DTDeviceKitBase 14:48:45.3154170 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DVTKit.framework/DVTKit 14:48:45.3159370 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DTDeviceKit.framework/DTDeviceKit 14:48:45.3160450 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DTXConnectionServices.framework/DTXConnectionServices 14:48:45.3167760 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DVTSourceControl.framework/DVTSourceControl 14:48:45.3168970 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DVTServices.framework/DVTServices 14:48:45.3170070 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DVTPortal.framework/DVTPortal 14:48:45.3236970 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DVTDocumentation.framework/DVTDocumentation 14:48:45.3244800 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DVTAnalyticsClient.framework/DVTAnalyticsClient 14:48:45.3254630 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/DVTAnalytics.framework/DVTAnalytics 14:48:45.3273940 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/SourceKit.framework/SourceKit 14:48:45.3451120 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/Frameworks/IDEFoundation.framework/IDEFoundation 14:48:45.3459620 Xamarin.Hosting: Loaded /Applications/Xcode113.app/Contents/SharedFrameworks/IDEProducts.framework/IDEProducts 14:48:45.4904370 Xamarin.Hosting: Simulator watchdogs are already disabled for 'iOS 13.3 (17C45) - iPhone X'. 14:48:45.5001330 Xamarin.Hosting: Launching simulator application 'com.apple.iphonesimulator' 14:48:45.8284390 Xamarin.Hosting: Ready notification 'com.apple.iphonesimulator.ready' received from the simulator. 14:48:45.8317100 Xamarin.Hosting: Booting iPhone X (iOS 13.3) - created by xharness... 14:48:46.4418970 Xamarin.Hosting: Booted iPhone X (iOS 13.3) - created by xharness successfully. 14:48:46.4461820 Copying in /Users/mandel/Xamarin/xamarin-macios/master/xamarin-macios/tests/xharness/tmp-test-dir/mscorlib Part 1/bin/mscorlib Part 1/iPhoneSimulator/Debug-unified/com.xamarin.bcltests.mscorlib Part 1.app/.monotouch-64 14:48:48.1443990 Xamarin.Hosting: No need to boot (already booted): iPhone X (iOS 13.3) - created by xharness 14:48:48.1459630 Xamarin.Hosting: Installing /Users/mandel/Xamarin/xamarin-macios/master/xamarin-macios/tests/xharness/tmp-test-dir/mscorlib Part 1/bin/mscorlib Part 1/iPhoneSimulator/Debug-unified/com.xamarin.bcltests.mscorlib Part 1.app with Bundle Identifier com.xamarin.bcltests.mscorlib Part 1 on 'iOS 13.3 (17C45) - iPhone X'... 14:49:25.8788620 Xamarin.Hosting: Installed 'com.xamarin.bcltests.mscorlib Part 1' from /Users/mandel/Xamarin/xamarin-macios/master/xamarin-macios/tests/xharness/tmp-test-dir/mscorlib Part 1/bin/mscorlib Part 1/iPhoneSimulator/Debug-unified/com.xamarin.bcltests.mscorlib Part 1.app 14:49:26.6020030 Xamarin.Hosting: Could not find any potentially troublesome weak load commands. 14:49:26.7037460 Xamarin.Hosting: The bundle id com.xamarin.bcltests.mscorlib Part 1 was successfully installed. 14:49:26.7087350 Xamarin.Hosting: Launching com.xamarin.bcltests.mscorlib Part 1 on 'iOS 13.3 (17C45) - iPhone X' 14:49:26.7808590 Xamarin.Hosting: Launched com.xamarin.bcltests.mscorlib Part 1 with pid 94797 14:49:26.7861460 Xamarin.Hosting: Device 'iOS 13.3 (17C45) - iPhone X' booted. 14:49:31.0250650 Connection from 127.0.0.1:53085 saving logs to /Users/mandel/Xamarin/xamarin-macios/master/xamarin-macios/jenkins-results/tests/20200320_144720/mscorlib Part 1/122/test-classic-20200320_144845.log 14:49:31.0257860 Test run started 14:50:01.4773140 Tests have finished executing 14:50:01.7897340 Xamarin.Hosting: Simulated process has exited. 14:50:02.0809610 Test run completed 14:50:02.1691940 Test run succeeded ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/simulators.xml ================================================  /Applications/Xcode113.app/Contents/Developer iOS 10.3 com.apple.CoreSimulator.SimRuntime.iOS-10-3 656129 iOS 12.2 com.apple.CoreSimulator.SimRuntime.iOS-12-2 786944 iOS {{MAX-IOS.VERSION}} com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} 852736 tvOS 10.2 com.apple.CoreSimulator.SimRuntime.tvOS-10-2 655872 tvOS 12.2 com.apple.CoreSimulator.SimRuntime.tvOS-12-2 786944 tvOS {{MAX-TVOS.VERSION}} com.apple.CoreSimulator.SimRuntime.tvOS-{{MAX-TVOS-VERSION}} 852736 watchOS 3.2 com.apple.CoreSimulator.SimRuntime.watchOS-3-2 197120 watchOS 5.2 com.apple.CoreSimulator.SimRuntime.watchOS-5-2 328192 watchOS {{MAX-WATCH.VERSION}} com.apple.CoreSimulator.SimRuntime.watchOS-{{MAX-WATCH-VERSION}} 393473 iPhone 4s com.apple.CoreSimulator.SimDeviceType.iPhone-4s IPhone 327680 655359 false iPhone 5 com.apple.CoreSimulator.SimDeviceType.iPhone-5 IPhone 393216 720895 false iPhone 5s com.apple.CoreSimulator.SimDeviceType.iPhone-5s IPhone 458752 851967 true iPhone 6 Plus com.apple.CoreSimulator.SimDeviceType.iPhone-6-Plus IPhone 524288 851967 true iPhone 6 com.apple.CoreSimulator.SimDeviceType.iPhone-6 IPhone 524288 851967 true iPhone 6s com.apple.CoreSimulator.SimDeviceType.iPhone-6s IPhone 589824 4294967295 true iPhone 6s Plus com.apple.CoreSimulator.SimDeviceType.iPhone-6s-Plus IPhone 589824 4294967295 true iPhone SE com.apple.CoreSimulator.SimDeviceType.iPhone-SE IPhone 590592 4294967295 true iPhone 7 com.apple.CoreSimulator.SimDeviceType.iPhone-7 IPhone 655360 4294967295 true iPhone 7 Plus com.apple.CoreSimulator.SimDeviceType.iPhone-7-Plus IPhone 655360 4294967295 true iPhone 8 com.apple.CoreSimulator.SimDeviceType.iPhone-8 IPhone 720896 4294967295 true iPhone 8 Plus com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus IPhone 720896 4294967295 true iPhone X com.apple.CoreSimulator.SimDeviceType.iPhone-X IPhone 720896 4294967295 true iPhone Xs com.apple.CoreSimulator.SimDeviceType.iPhone-XS IPhone 786432 4294967295 true iPhone Xs Max com.apple.CoreSimulator.SimDeviceType.iPhone-XS-Max IPhone 786432 4294967295 true iPhone Xʀ com.apple.CoreSimulator.SimDeviceType.iPhone-XR IPhone 786432 4294967295 true iPhone 11 com.apple.CoreSimulator.SimDeviceType.iPhone-11 IPhone 851968 4294967295 true iPhone 11 Pro com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro IPhone 851968 4294967295 true iPhone 11 Pro Max com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro-Max IPhone 851968 4294967295 true iPad 2 com.apple.CoreSimulator.SimDeviceType.iPad-2 IPad 262912 655359 false iPad Retina com.apple.CoreSimulator.SimDeviceType.iPad-Retina IPad 327936 655359 false iPad Air com.apple.CoreSimulator.SimDeviceType.iPad-Air IPad 458752 851967 true iPad mini 2 com.apple.CoreSimulator.SimDeviceType.iPad-mini-2 IPad 458752 851967 true iPad mini 3 com.apple.CoreSimulator.SimDeviceType.iPad-mini-3 IPad 524544 851967 true iPad mini 4 com.apple.CoreSimulator.SimDeviceType.iPad-mini-4 IPad 589824 4294967295 true iPad Air 2 com.apple.CoreSimulator.SimDeviceType.iPad-Air-2 IPad 524288 4294967295 true iPad Pro (9.7-inch) com.apple.CoreSimulator.SimDeviceType.iPad-Pro--9-7-inch- IPad 655360 4294967295 true iPad Pro (12.9-inch) com.apple.CoreSimulator.SimDeviceType.iPad-Pro IPad 590080 4294967295 true iPad (5th generation) com.apple.CoreSimulator.SimDeviceType.iPad--5th-generation- IPad 656128 4294967295 true iPad Pro (12.9-inch) (2nd generation) com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---2nd-generation- IPad 656128 4294967295 true iPad Pro (10.5-inch) com.apple.CoreSimulator.SimDeviceType.iPad-Pro--10-5-inch- IPad 656128 4294967295 true iPad (6th generation) com.apple.CoreSimulator.SimDeviceType.iPad--6th-generation- IPad 721664 4294967295 true iPad (7th generation) com.apple.CoreSimulator.SimDeviceType.iPad--7th-generation- IPad 851968 4294967295 true iPad Pro (11-inch) com.apple.CoreSimulator.SimDeviceType.iPad-Pro--11-inch- IPad 786432 4294967295 true iPad Pro (12.9-inch) (3rd generation) com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---3rd-generation- IPad 786432 4294967295 true iPad mini (5th generation) com.apple.CoreSimulator.SimDeviceType.iPad-mini--5th-generation- IPad 786944 4294967295 true iPad Air (3rd generation) com.apple.CoreSimulator.SimDeviceType.iPad-Air--3rd-generation- IPad 786944 4294967295 true Apple TV com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p TV 589824 4294967295 true Apple TV 4K com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-4K TV 720896 4294967295 true Apple TV 4K (at 1080p) com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-1080p TV 720896 4294967295 true Apple Watch - 38mm com.apple.CoreSimulator.SimDeviceType.Apple-Watch-38mm Watch 131072 327679 false Apple Watch - 42mm com.apple.CoreSimulator.SimDeviceType.Apple-Watch-42mm Watch 131072 327679 false Apple Watch Series 2 - 38mm com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-38mm Watch 196608 4294967295 false Apple Watch Series 2 - 42mm com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-42mm Watch 196608 4294967295 false Apple Watch Series 3 - 38mm com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-3-38mm Watch 262144 4294967295 false Apple Watch Series 3 - 42mm com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-3-42mm Watch 262144 4294967295 false Apple Watch Series 4 - 40mm com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-4-40mm Watch 327680 4294967295 false Apple Watch Series 4 - 44mm com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-4-44mm Watch 327680 4294967295 false Apple Watch Series 5 - 40mm com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-5-40mm Watch 393216 4294967295 false Apple Watch Series 5 - 44mm com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-5-44mm Watch 393216 4294967295 false com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPhone-5 /Users/mandel/Library/Developer/CoreSimulator/Devices/B0CF5BA5-318E-4FC8-8FC1-FB82D9F2FB42 /Users/mandel/Library/Logs/CoreSimulator/B0CF5BA5-318E-4FC8-8FC1-FB82D9F2FB42 com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPhone-5s /Users/mandel/Library/Developer/CoreSimulator/Devices/78A86F96-8333-42DE-81F3-1D8CFEC1FB17 /Users/mandel/Library/Logs/CoreSimulator/78A86F96-8333-42DE-81F3-1D8CFEC1FB17 com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPhone-6-Plus /Users/mandel/Library/Developer/CoreSimulator/Devices/A921DF18-F3A1-4EDC-90DA-20E85D94685C /Users/mandel/Library/Logs/CoreSimulator/A921DF18-F3A1-4EDC-90DA-20E85D94685C com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPhone-6 /Users/mandel/Library/Developer/CoreSimulator/Devices/62992D4C-C669-4C51-AAD4-1877891EC26B /Users/mandel/Library/Logs/CoreSimulator/62992D4C-C669-4C51-AAD4-1877891EC26B com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPhone-6s /Users/mandel/Library/Developer/CoreSimulator/Devices/5C41C297-EDF4-4D97-804C-4186843CAC71 /Users/mandel/Library/Logs/CoreSimulator/5C41C297-EDF4-4D97-804C-4186843CAC71 com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPhone-6s-Plus /Users/mandel/Library/Developer/CoreSimulator/Devices/619E6E2A-1B6E-42AC-93A5-2EE548689274 /Users/mandel/Library/Logs/CoreSimulator/619E6E2A-1B6E-42AC-93A5-2EE548689274 com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPhone-SE /Users/mandel/Library/Developer/CoreSimulator/Devices/B239B550-AB0B-416C-987B-9AD8A856D429 /Users/mandel/Library/Logs/CoreSimulator/B239B550-AB0B-416C-987B-9AD8A856D429 com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPhone-7 /Users/mandel/Library/Developer/CoreSimulator/Devices/216D7E4C-679F-4E57-9680-805930EA9D1E /Users/mandel/Library/Logs/CoreSimulator/216D7E4C-679F-4E57-9680-805930EA9D1E com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPhone-7-Plus /Users/mandel/Library/Developer/CoreSimulator/Devices/71AFDC06-B30E-4B2D-BF63-0EDDA0A62063 /Users/mandel/Library/Logs/CoreSimulator/71AFDC06-B30E-4B2D-BF63-0EDDA0A62063 com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPad-Air /Users/mandel/Library/Developer/CoreSimulator/Devices/8C322CFC-A086-454E-BCA6-2BFC86E60B5E /Users/mandel/Library/Logs/CoreSimulator/8C322CFC-A086-454E-BCA6-2BFC86E60B5E com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPad-Air-2 /Users/mandel/Library/Developer/CoreSimulator/Devices/FD05BCC6-2FE6-4F06-B954-5C835D10F14A /Users/mandel/Library/Logs/CoreSimulator/FD05BCC6-2FE6-4F06-B954-5C835D10F14A com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPad-Pro--9-7-inch- /Users/mandel/Library/Developer/CoreSimulator/Devices/0D858881-E7D8-47CE-A972-CB93727A1184 /Users/mandel/Library/Logs/CoreSimulator/0D858881-E7D8-47CE-A972-CB93727A1184 com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPad-Pro /Users/mandel/Library/Developer/CoreSimulator/Devices/C5DF41A6-4AB2-4427-B4C3-504D59D1D6A2 /Users/mandel/Library/Logs/CoreSimulator/C5DF41A6-4AB2-4427-B4C3-504D59D1D6A2 com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPad--5th-generation- /Users/mandel/Library/Developer/CoreSimulator/Devices/19086AC5-F933-43BD-AEA6-1C1D0737E3DB /Users/mandel/Library/Logs/CoreSimulator/19086AC5-F933-43BD-AEA6-1C1D0737E3DB com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---2nd-generation- /Users/mandel/Library/Developer/CoreSimulator/Devices/1688E430-E910-46EC-980C-A281C8BA8AAE /Users/mandel/Library/Logs/CoreSimulator/1688E430-E910-46EC-980C-A281C8BA8AAE com.apple.CoreSimulator.SimRuntime.iOS-10-3 com.apple.CoreSimulator.SimDeviceType.iPad-Pro--10-5-inch- /Users/mandel/Library/Developer/CoreSimulator/Devices/356B263C-8516-4F3B-BBF1-CA23FA5478E6 /Users/mandel/Library/Logs/CoreSimulator/356B263C-8516-4F3B-BBF1-CA23FA5478E6 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-5s /Users/mandel/Library/Developer/CoreSimulator/Devices/711BA51B-6261-4394-84A6-BE9F8B5D3B80 /Users/mandel/Library/Logs/CoreSimulator/711BA51B-6261-4394-84A6-BE9F8B5D3B80 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-6-Plus /Users/mandel/Library/Developer/CoreSimulator/Devices/1D4E284E-E1F7-48BE-9975-E90C47A39B7C /Users/mandel/Library/Logs/CoreSimulator/1D4E284E-E1F7-48BE-9975-E90C47A39B7C com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-6 /Users/mandel/Library/Developer/CoreSimulator/Devices/0FE1F639-BE0D-42A8-BAE9-09D8656051D4 /Users/mandel/Library/Logs/CoreSimulator/0FE1F639-BE0D-42A8-BAE9-09D8656051D4 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-6s /Users/mandel/Library/Developer/CoreSimulator/Devices/08ED135B-B33B-439E-B188-2F84C1B478FC /Users/mandel/Library/Logs/CoreSimulator/08ED135B-B33B-439E-B188-2F84C1B478FC com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-6s-Plus /Users/mandel/Library/Developer/CoreSimulator/Devices/96A46A7E-0F6E-4627-A309-9306111A1B33 /Users/mandel/Library/Logs/CoreSimulator/96A46A7E-0F6E-4627-A309-9306111A1B33 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-SE /Users/mandel/Library/Developer/CoreSimulator/Devices/04DF2D48-4442-46EE-B3BE-695E403136C5 /Users/mandel/Library/Logs/CoreSimulator/04DF2D48-4442-46EE-B3BE-695E403136C5 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-7 /Users/mandel/Library/Developer/CoreSimulator/Devices/79AB7EDF-3FCB-4D2C-A390-7BB5936736B4 /Users/mandel/Library/Logs/CoreSimulator/79AB7EDF-3FCB-4D2C-A390-7BB5936736B4 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-7-Plus /Users/mandel/Library/Developer/CoreSimulator/Devices/204656B8-0240-43FE-BF99-3B0F284FFB3C /Users/mandel/Library/Logs/CoreSimulator/204656B8-0240-43FE-BF99-3B0F284FFB3C com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-8 /Users/mandel/Library/Developer/CoreSimulator/Devices/FFFC5F6C-49EE-4796-8881-3416AE5F4708 /Users/mandel/Library/Logs/CoreSimulator/FFFC5F6C-49EE-4796-8881-3416AE5F4708 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus /Users/mandel/Library/Developer/CoreSimulator/Devices/AD320A80-FF13-4D28-8346-7962005732B0 /Users/mandel/Library/Logs/CoreSimulator/AD320A80-FF13-4D28-8346-7962005732B0 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-X /Users/mandel/Library/Developer/CoreSimulator/Devices/2FD14929-A33B-4690-86CD-42716A4DF2ED /Users/mandel/Library/Logs/CoreSimulator/2FD14929-A33B-4690-86CD-42716A4DF2ED com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-XS /Users/mandel/Library/Developer/CoreSimulator/Devices/E9914E48-7A63-493D-B7AA-D7AD90F0E312 /Users/mandel/Library/Logs/CoreSimulator/E9914E48-7A63-493D-B7AA-D7AD90F0E312 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-XS-Max /Users/mandel/Library/Developer/CoreSimulator/Devices/FA0C5B13-CC3F-4044-8E7E-48100375A340 /Users/mandel/Library/Logs/CoreSimulator/FA0C5B13-CC3F-4044-8E7E-48100375A340 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPhone-XR /Users/mandel/Library/Developer/CoreSimulator/Devices/BB062731-8AA4-4A3A-A2A6-D032FC41F3B9 /Users/mandel/Library/Logs/CoreSimulator/BB062731-8AA4-4A3A-A2A6-D032FC41F3B9 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPad-Air /Users/mandel/Library/Developer/CoreSimulator/Devices/9E8F8D70-7180-495B-853C-D51871E9F102 /Users/mandel/Library/Logs/CoreSimulator/9E8F8D70-7180-495B-853C-D51871E9F102 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPad-Air-2 /Users/mandel/Library/Developer/CoreSimulator/Devices/4505497B-509A-4246-987D-119064149BF6 /Users/mandel/Library/Logs/CoreSimulator/4505497B-509A-4246-987D-119064149BF6 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPad-Pro--9-7-inch- /Users/mandel/Library/Developer/CoreSimulator/Devices/09D14A0A-4683-4E42-98A2-AB53310F197A /Users/mandel/Library/Logs/CoreSimulator/09D14A0A-4683-4E42-98A2-AB53310F197A com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPad-Pro /Users/mandel/Library/Developer/CoreSimulator/Devices/1544537F-FDC8-40C9-AC08-A09FC4DD24B6 /Users/mandel/Library/Logs/CoreSimulator/1544537F-FDC8-40C9-AC08-A09FC4DD24B6 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPad--5th-generation- /Users/mandel/Library/Developer/CoreSimulator/Devices/A83C8C66-A7CF-46FD-8E4B-3A87FD2FF5A4 /Users/mandel/Library/Logs/CoreSimulator/A83C8C66-A7CF-46FD-8E4B-3A87FD2FF5A4 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---2nd-generation- /Users/mandel/Library/Developer/CoreSimulator/Devices/257CA31A-CC5A-4C5C-B072-0D3A3D0A9451 /Users/mandel/Library/Logs/CoreSimulator/257CA31A-CC5A-4C5C-B072-0D3A3D0A9451 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPad-Pro--10-5-inch- /Users/mandel/Library/Developer/CoreSimulator/Devices/C006BEA4-CEDF-46E2-959C-96B8C9E6B6BA /Users/mandel/Library/Logs/CoreSimulator/C006BEA4-CEDF-46E2-959C-96B8C9E6B6BA com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPad--6th-generation- /Users/mandel/Library/Developer/CoreSimulator/Devices/43F3F691-8B57-47D6-898B-B2AAFCA53BFA /Users/mandel/Library/Logs/CoreSimulator/43F3F691-8B57-47D6-898B-B2AAFCA53BFA com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPad-Pro--11-inch- /Users/mandel/Library/Developer/CoreSimulator/Devices/3DF2CFFC-C279-485D-8379-CC50C2297EDD /Users/mandel/Library/Logs/CoreSimulator/3DF2CFFC-C279-485D-8379-CC50C2297EDD com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---3rd-generation- /Users/mandel/Library/Developer/CoreSimulator/Devices/4F2E05BB-4945-445E-99E7-D81D4F83E248 /Users/mandel/Library/Logs/CoreSimulator/4F2E05BB-4945-445E-99E7-D81D4F83E248 com.apple.CoreSimulator.SimRuntime.iOS-12-2 com.apple.CoreSimulator.SimDeviceType.iPad-Air--3rd-generation- /Users/mandel/Library/Developer/CoreSimulator/Devices/02177DD5-3EE1-4A8D-8277-BE95F4ACB1A5 /Users/mandel/Library/Logs/CoreSimulator/02177DD5-3EE1-4A8D-8277-BE95F4ACB1A5 com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPhone-8 /Users/mandel/Library/Developer/CoreSimulator/Devices/CFD33BF1-FC9A-4674-BB49-80CFA47450A5 /Users/mandel/Library/Logs/CoreSimulator/CFD33BF1-FC9A-4674-BB49-80CFA47450A5 com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus /Users/mandel/Library/Developer/CoreSimulator/Devices/9D61D008-5A27-4B98-801A-0709897EA981 /Users/mandel/Library/Logs/CoreSimulator/9D61D008-5A27-4B98-801A-0709897EA981 com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPhone-X /Users/mandel/Library/Developer/CoreSimulator/Devices/18F485DC-7F0A-440F-BEF9-5BE5954B0FB1 /Users/mandel/Library/Logs/CoreSimulator/18F485DC-7F0A-440F-BEF9-5BE5954B0FB1 com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPhone-XS /Users/mandel/Library/Developer/CoreSimulator/Devices/336E1D83-AB00-4FAE-A70D-C516D269B90A /Users/mandel/Library/Logs/CoreSimulator/336E1D83-AB00-4FAE-A70D-C516D269B90A com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPhone-11 /Users/mandel/Library/Developer/CoreSimulator/Devices/ABC2A44A-789D-4C8B-88A7-0CD7C6BF316C /Users/mandel/Library/Logs/CoreSimulator/ABC2A44A-789D-4C8B-88A7-0CD7C6BF316C com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro /Users/mandel/Library/Developer/CoreSimulator/Devices/1840A231-2033-42A6-AEB4-B223235A2707 /Users/mandel/Library/Logs/CoreSimulator/1840A231-2033-42A6-AEB4-B223235A2707 com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro-Max /Users/mandel/Library/Developer/CoreSimulator/Devices/FB4347FF-11B6-4487-BECC-0B556BAD3E89 /Users/mandel/Library/Logs/CoreSimulator/FB4347FF-11B6-4487-BECC-0B556BAD3E89 com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPad-Pro--9-7-inch- /Users/mandel/Library/Developer/CoreSimulator/Devices/972FB6FB-6BC5-4FD8-B8FB-BC4B08DAC817 /Users/mandel/Library/Logs/CoreSimulator/972FB6FB-6BC5-4FD8-B8FB-BC4B08DAC817 com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPad--7th-generation- /Users/mandel/Library/Developer/CoreSimulator/Devices/C519CBBA-AAAD-4777-B599-8A30085AABCB /Users/mandel/Library/Logs/CoreSimulator/C519CBBA-AAAD-4777-B599-8A30085AABCB com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPad-Pro--11-inch- /Users/mandel/Library/Developer/CoreSimulator/Devices/F7FCC57E-97CA-410F-B759-DFB395F0062C /Users/mandel/Library/Logs/CoreSimulator/F7FCC57E-97CA-410F-B759-DFB395F0062C com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---3rd-generation- /Users/mandel/Library/Developer/CoreSimulator/Devices/80096DEA-A0BC-4755-8AB4-F941B6DD7438 /Users/mandel/Library/Logs/CoreSimulator/80096DEA-A0BC-4755-8AB4-F941B6DD7438 com.apple.CoreSimulator.SimRuntime.iOS-{{MAX-IOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.iPad-Air--3rd-generation- /Users/mandel/Library/Developer/CoreSimulator/Devices/8094F147-86A1-4196-BFB9-040BAC65B9BF /Users/mandel/Library/Logs/CoreSimulator/8094F147-86A1-4196-BFB9-040BAC65B9BF com.apple.CoreSimulator.SimRuntime.tvOS-10-2 com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p /Users/mandel/Library/Developer/CoreSimulator/Devices/FC0AD7D8-40E1-4BEC-BC84-ECE1796E3B10 /Users/mandel/Library/Logs/CoreSimulator/FC0AD7D8-40E1-4BEC-BC84-ECE1796E3B10 com.apple.CoreSimulator.SimRuntime.tvOS-12-2 com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p /Users/mandel/Library/Developer/CoreSimulator/Devices/62179381-3483-41F2-ABD2-7C69E46B2C3C /Users/mandel/Library/Logs/CoreSimulator/62179381-3483-41F2-ABD2-7C69E46B2C3C com.apple.CoreSimulator.SimRuntime.tvOS-12-2 com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-4K /Users/mandel/Library/Developer/CoreSimulator/Devices/C10D4992-1C46-474D-871B-B89D0817CFB4 /Users/mandel/Library/Logs/CoreSimulator/C10D4992-1C46-474D-871B-B89D0817CFB4 com.apple.CoreSimulator.SimRuntime.tvOS-12-2 com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-1080p /Users/mandel/Library/Developer/CoreSimulator/Devices/73EAFA5D-04C7-4194-8D51-6E32C913C3B6 /Users/mandel/Library/Logs/CoreSimulator/73EAFA5D-04C7-4194-8D51-6E32C913C3B6 com.apple.CoreSimulator.SimRuntime.tvOS-{{MAX-TVOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p /Users/mandel/Library/Developer/CoreSimulator/Devices/3A12BAFF-0DAA-4052-AB0D-743AB85CE030 /Users/mandel/Library/Logs/CoreSimulator/3A12BAFF-0DAA-4052-AB0D-743AB85CE030 com.apple.CoreSimulator.SimRuntime.tvOS-{{MAX-TVOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-4K /Users/mandel/Library/Developer/CoreSimulator/Devices/FD2A3BE2-3E95-473E-99D2-DF0733F1CEED /Users/mandel/Library/Logs/CoreSimulator/FD2A3BE2-3E95-473E-99D2-DF0733F1CEED com.apple.CoreSimulator.SimRuntime.tvOS-{{MAX-TVOS-VERSION}} com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-1080p /Users/mandel/Library/Developer/CoreSimulator/Devices/2599FF00-17DD-43F7-BFC4-90693C145398 /Users/mandel/Library/Logs/CoreSimulator/2599FF00-17DD-43F7-BFC4-90693C145398 com.apple.CoreSimulator.SimRuntime.watchOS-3-2 com.apple.CoreSimulator.SimDeviceType.Apple-Watch-38mm /Users/mandel/Library/Developer/CoreSimulator/Devices/F00FA51A-D353-4DD0-9631-FBAE0CE79D0F /Users/mandel/Library/Logs/CoreSimulator/F00FA51A-D353-4DD0-9631-FBAE0CE79D0F com.apple.CoreSimulator.SimRuntime.watchOS-3-2 com.apple.CoreSimulator.SimDeviceType.Apple-Watch-42mm /Users/mandel/Library/Developer/CoreSimulator/Devices/7E4D7EC2-45C3-41A5-A7CD-78BE9610E02B /Users/mandel/Library/Logs/CoreSimulator/7E4D7EC2-45C3-41A5-A7CD-78BE9610E02B com.apple.CoreSimulator.SimRuntime.watchOS-3-2 com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-38mm /Users/mandel/Library/Developer/CoreSimulator/Devices/7209E5D6-9F1B-4067-96D7-342D403CD805 /Users/mandel/Library/Logs/CoreSimulator/7209E5D6-9F1B-4067-96D7-342D403CD805 com.apple.CoreSimulator.SimRuntime.watchOS-3-2 com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-42mm /Users/mandel/Library/Developer/CoreSimulator/Devices/7BDF181F-3D32-4ECB-B2D6-0D73353DD0AB /Users/mandel/Library/Logs/CoreSimulator/7BDF181F-3D32-4ECB-B2D6-0D73353DD0AB com.apple.CoreSimulator.SimRuntime.watchOS-5-2 com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-38mm /Users/mandel/Library/Developer/CoreSimulator/Devices/99F89C62-A028-4752-96D3-5E57CDAEDA22 /Users/mandel/Library/Logs/CoreSimulator/99F89C62-A028-4752-96D3-5E57CDAEDA22 com.apple.CoreSimulator.SimRuntime.watchOS-5-2 com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-42mm /Users/mandel/Library/Developer/CoreSimulator/Devices/8DF7353A-CE2C-45E2-944D-FF12FBFBF24B /Users/mandel/Library/Logs/CoreSimulator/8DF7353A-CE2C-45E2-944D-FF12FBFBF24B com.apple.CoreSimulator.SimRuntime.watchOS-5-2 com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-3-38mm /Users/mandel/Library/Developer/CoreSimulator/Devices/3F577C08-9F99-4B9F-8EFC-CFF64B24DB51 /Users/mandel/Library/Logs/CoreSimulator/3F577C08-9F99-4B9F-8EFC-CFF64B24DB51 com.apple.CoreSimulator.SimRuntime.watchOS-5-2 com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-3-42mm /Users/mandel/Library/Developer/CoreSimulator/Devices/E26C48DB-9780-4342-84E2-783F4FF86286 /Users/mandel/Library/Logs/CoreSimulator/E26C48DB-9780-4342-84E2-783F4FF86286 com.apple.CoreSimulator.SimRuntime.watchOS-5-2 com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-4-40mm /Users/mandel/Library/Developer/CoreSimulator/Devices/121B2CE2-B036-4BB4-B54A-A03F5431091B /Users/mandel/Library/Logs/CoreSimulator/121B2CE2-B036-4BB4-B54A-A03F5431091B com.apple.CoreSimulator.SimRuntime.watchOS-5-2 com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-4-44mm /Users/mandel/Library/Developer/CoreSimulator/Devices/7764F572-0B81-4353-9D19-AA5E358425C2 /Users/mandel/Library/Logs/CoreSimulator/7764F572-0B81-4353-9D19-AA5E358425C2 com.apple.CoreSimulator.SimRuntime.watchOS-{{MAX-WATCH-VERSION}} com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-3-38mm /Users/mandel/Library/Developer/CoreSimulator/Devices/BB2F9CF0-A928-4909-867A-525A937D7390 /Users/mandel/Library/Logs/CoreSimulator/BB2F9CF0-A928-4909-867A-525A937D7390 com.apple.CoreSimulator.SimRuntime.watchOS-{{MAX-WATCH-VERSION}} com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-3-38mm /Users/mandel/Library/Developer/CoreSimulator/Devices/D9FC8E35-8CD2-4E3F-A660-24CAF1304479 /Users/mandel/Library/Logs/CoreSimulator/D9FC8E35-8CD2-4E3F-A660-24CAF1304479 com.apple.CoreSimulator.SimRuntime.watchOS-{{MAX-WATCH-VERSION}} com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-4-40mm /Users/mandel/Library/Developer/CoreSimulator/Devices/8EC34527-72DB-4A4D-B268-79C838189492 /Users/mandel/Library/Logs/CoreSimulator/8EC34527-72DB-4A4D-B268-79C838189492 com.apple.CoreSimulator.SimRuntime.watchOS-{{MAX-WATCH-VERSION}} com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-4-44mm /Users/mandel/Library/Developer/CoreSimulator/Devices/CC509130-B1EC-4400-B2AB-730B602F7E03 /Users/mandel/Library/Logs/CoreSimulator/CC509130-B1EC-4400-B2AB-730B602F7E03 com.apple.CoreSimulator.SimRuntime.watchOS-{{MAX-WATCH-VERSION}} com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-5-40mm /Users/mandel/Library/Developer/CoreSimulator/Devices/9B798995-5A29-45E1-BF89-6D29BB2864D3 /Users/mandel/Library/Logs/CoreSimulator/9B798995-5A29-45E1-BF89-6D29BB2864D3 com.apple.CoreSimulator.SimRuntime.watchOS-{{MAX-WATCH-VERSION}} com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-5-44mm /Users/mandel/Library/Developer/CoreSimulator/Devices/77F07B96-D2E4-4F04-B958-B68E2FBCC2ED /Users/mandel/Library/Logs/CoreSimulator/77F07B96-D2E4-4F04-B958-B68E2FBCC2ED 62992D4C-C669-4C51-AAD4-1877891EC26B F00FA51A-D353-4DD0-9631-FBAE0CE79D0F 18F485DC-7F0A-440F-BEF9-5BE5954B0FB1 BB2F9CF0-A928-4909-867A-525A937D7390 336E1D83-AB00-4FAE-A70D-C516D269B90A BB2F9CF0-A928-4909-867A-525A937D7390 1840A231-2033-42A6-AEB4-B223235A2707 9B798995-5A29-45E1-BF89-6D29BB2864D3 FB4347FF-11B6-4487-BECC-0B556BAD3E89 77F07B96-D2E4-4F04-B958-B68E2FBCC2ED ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Samples/xUnitSample.xml ================================================  ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/TestExecutingResultTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Linq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests; public class TestExecutingResultTests { [Theory] [InlineData( new[] { TestExecutingResult.Crashed, TestExecutingResult.TimedOut, TestExecutingResult.HarnessException, TestExecutingResult.LaunchFailure, TestExecutingResult.BuildFailure, TestExecutingResult.LaunchTimedOut, TestExecutingResult.Failed, }, TestExecutingResult.Failed )] [InlineData( new[] { TestExecutingResult.Building, TestExecutingResult.BuildQueued, TestExecutingResult.Built, TestExecutingResult.Running, TestExecutingResult.RunQueued, TestExecutingResult.InProgress, TestExecutingResult.StateMask, }, TestExecutingResult.InProgress )] [InlineData( new[] { TestExecutingResult.Succeeded, TestExecutingResult.BuildSucceeded, }, TestExecutingResult.Succeeded )] public void FlagIsPresentWhereItShouldBe(TestExecutingResult[] withFlag, TestExecutingResult flag) { var withoutFlag = Enum.GetValues(typeof(TestExecutingResult)) .Cast() .Except(withFlag); foreach (var result in withoutFlag) { Assert.False(result.HasFlag(flag), $"{result} should not have {flag}"); } foreach (var result in withFlag) { Assert.True(result.HasFlag(flag), $"{result} should have {flag}"); } } [Theory] [InlineData( TestExecutingResult.LaunchTimedOut, new[] { TestExecutingResult.TimedOut, TestExecutingResult.LaunchFailure, TestExecutingResult.Failed, } )] public void ResultHasFlag(TestExecutingResult result, TestExecutingResult[] flags) { foreach (var flag in flags) { Assert.True(result.HasFlag(flag), $"{result} should have {flag}"); } } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/TestReporterTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.Execution; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Execution; using Microsoft.DotNet.XHarness.iOS.Shared.Listeners; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests; public class TestReporterTests : IDisposable { private readonly Mock _crashReporter; private readonly Mock _processManager; private readonly IResultParser _parser; private readonly Mock _runLog; private readonly Mock _mainLog; private readonly Mock _logs; private readonly Mock _listener; private readonly AppBundleInformation _appInformation; private readonly string _deviceName = "Device Name"; private readonly string _logsDirectory; public TestReporterTests() { _crashReporter = new Mock(); _processManager = new Mock(); _parser = new XmlResultParser(); _runLog = new Mock(); _mainLog = new Mock(); _logs = new Mock(); _listener = new Mock(); _appInformation = new AppBundleInformation( appName: "test app", bundleIdentifier: "my.id.com", appPath: "/path/to/app", launchAppPath: "/launch/app/path", supports32b: false, extension: null) { Variation = "Debug" }; _logsDirectory = Path.GetTempFileName(); File.Delete(_logsDirectory); Directory.CreateDirectory(_logsDirectory); } public void Dispose() { if (Directory.Exists(_logsDirectory)) { Directory.Delete(_logsDirectory, true); } GC.SuppressFinalize(this); } private Stream GetRunLogSample() { var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith("run-log.txt", StringComparison.Ordinal)).FirstOrDefault(); return GetType().Assembly.GetManifestResourceStream(name); } private TestReporter BuildTestReporter() { _logs.Setup(l => l.Directory).Returns(_logsDirectory); return new TestReporter(_processManager.Object, _mainLog.Object, _runLog.Object, _logs.Object, _crashReporter.Object, _listener.Object, _parser, _appInformation, RunMode.iOS, XmlResultJargon.NUnitV3, _deviceName, TimeSpan.FromSeconds(2)); } [Fact] public async Task CollectSimulatorResultsSucess() { // set the listener to return a task that we are not going to complete var cancellationTokenSource = new CancellationTokenSource(); var tcs = new TaskCompletionSource(); _listener.Setup(l => l.CompletionTask).Returns(tcs.Task); // will never be set to be completed // ensure that we do provide the required runlog information so that we know if it was a launch failure or not, we are // not dealing with the launch faliure _runLog.Setup(l => l.GetReader()).Returns(new StreamReader(GetRunLogSample())); var testReporter = BuildTestReporter(); var processResult = new ProcessExecutionResult() { TimedOut = false, ExitCode = 0 }; await testReporter.CollectSimulatorResult(processResult); // we should have timeout, since the task completion source was never set Assert.True(testReporter.Success, "success"); _processManager.Verify(p => p.KillTreeAsync(It.IsAny(), It.IsAny(), true), Times.Never); } // we need to make sure that we take into account the case in which we do have data, but no PID and an empty file // which is a catastrophic launch error [Theory] [InlineData("Some Data")] [InlineData(null)] public async Task CollectSimulatorResultsLaunchFailureTest(string runLogData) { // similar to the above test, but in this case we ware going to fake a launch issue, that is, the runlog // does not contain a PID that we can parse and later try to kill. // set the listener to return a task that we are not going to complete var cancellationTokenSource = new CancellationTokenSource(); var tcs = new TaskCompletionSource(); _listener.Setup(l => l.CompletionTask).Returns(tcs.Task); // will never be set to be completed // empty test file to be returned as the runlog stream var tmpFile = Path.GetTempFileName(); if (!string.IsNullOrEmpty(runLogData)) { using (var writer = new StreamWriter(tmpFile)) { writer.Write(runLogData); } } // ensure that we do provide the required runlog information so that we know if it was a launch failure or not, we are // not dealing with the launch faliure _runLog.Setup(l => l.GetReader()).Returns(new StreamReader(File.Create(tmpFile))); var testReporter = BuildTestReporter(); var processResult = new ProcessExecutionResult() { TimedOut = true, ExitCode = 0 }; await testReporter.CollectSimulatorResult(processResult); // we should have timeout, since the task completion source was never set Assert.False(testReporter.Success, "success"); // verify that we do not try to kill a process that never got started _processManager.Verify(p => p.KillTreeAsync(It.IsAny(), It.IsAny(), true), Times.Never); File.Delete(tmpFile); } [Fact] public async Task CollectSimulatorResult_WhenConnectedTaskCanceledAndRunLogEmpty_DoesNotThrowAndTreatsAsLaunchFailure() { var connectedTask = new TaskCompletionSource(); connectedTask.TrySetCanceled(); _listener.Setup(l => l.ConnectedTask).Returns(connectedTask.Task); var listenerLog = Mock.Of(l => l.FullPath == "/this/path/does/not/exist"); _listener.Setup(l => l.TestLog).Returns(listenerLog); var runLogPath = Path.GetTempFileName(); File.WriteAllText(runLogPath, string.Empty); _runLog .Setup(l => l.GetReader()) .Returns(() => new StreamReader(File.OpenRead(runLogPath))); var stderr = Path.GetTempFileName(); _mainLog.Setup(l => l.FullPath).Returns(stderr); _logs .Setup(l => l.Create(It.IsAny(), It.IsAny(), It.IsAny())) .Returns((string filename, string description, bool? timestamp) => Mock.Of(l => l.FullPath == Path.Combine(_logsDirectory, filename))); var testReporter = BuildTestReporter(); var processResult = new ProcessExecutionResult() { TimedOut = false, ExitCode = 1 }; var exception = await Record.ExceptionAsync(() => testReporter.CollectSimulatorResult(processResult)); Assert.Null(exception); Assert.False(testReporter.Success, "success"); var (result, resultMessage) = await testReporter.ParseResult(); Assert.Equal(TestExecutingResult.LaunchFailure, result); Assert.Equal("Launch failure", resultMessage); _mainLog.Verify(l => l.WriteLine(It.Is(s => s.Equals("Could not find pid in mtouch output."))), Times.Once); _mainLog.Verify(l => l.WriteLine(It.Is(s => s.Equals("Test run failed to launch"))), Times.Once); _processManager.Verify(p => p.KillTreeAsync(It.IsAny(), It.IsAny(), true), Times.Never); File.Delete(runLogPath); File.Delete(stderr); } [Theory] [InlineData(0)] [InlineData(1)] public async Task CollectSimulatorResultsSuccessLaunchTest(int processExitCode) { // fake the best case scenario, we got the process to exit correctly var cancellationTokenSource = new CancellationTokenSource(); var tcs = new TaskCompletionSource(); var processResult = new ProcessExecutionResult() { TimedOut = false, ExitCode = processExitCode }; // ensure we do not consider it to be a launch failure _runLog.Setup(l => l.GetReader()).Returns(new StreamReader(GetRunLogSample())); var testReporter = BuildTestReporter(); await testReporter.CollectSimulatorResult(processResult); // we should have timeout, since the task completion source was never set if (processExitCode != 0) { Assert.False(testReporter.Success, "success"); } else { Assert.True(testReporter.Success, "success"); } if (processExitCode != 0) { _processManager.Verify(p => p.KillTreeAsync(It.IsAny(), It.IsAny(), true), Times.Once); } else { // verify that we do not try to kill a process that never got started _processManager.Verify(p => p.KillTreeAsync(It.IsAny(), It.IsAny(), true), Times.Never); } } [Fact] public async Task CollectDeviceResultTimeoutTest() { // set the listener to return a task that we are not going to complete var tcs = new TaskCompletionSource(); _listener.Setup(l => l.CompletionTask).Returns(tcs.Task); // will never be set to be completed // ensure that we do provide the required runlog information so that we know if it was a launch failure or not, we are // not dealing with the launch faliure _runLog.Setup(l => l.GetReader()).Returns(new StreamReader(GetRunLogSample())); var testReporter = BuildTestReporter(); var processResult = new ProcessExecutionResult() { TimedOut = true, ExitCode = 0 }; await testReporter.CollectDeviceResult(processResult); // we should have timeout, since the task completion source was never set Assert.False(testReporter.Success, "success"); } [Theory] [InlineData(0)] [InlineData(1)] public async Task CollectDeviceResultSuccessTest(int processExitCode) { // fake the best case scenario, we got the process to exit correctly var processResult = new ProcessExecutionResult() { TimedOut = false, ExitCode = processExitCode }; // ensure we do not consider it to be a launch failure _runLog.Setup(l => l.GetReader()).Returns(new StreamReader(GetRunLogSample())); var testReporter = BuildTestReporter(); await testReporter.CollectDeviceResult(processResult); // we should have timeout, since the task completion source was never set if (processExitCode != 0) { Assert.False(testReporter.Success, "success"); } else { Assert.True(testReporter.Success, "success"); } } [Fact] public void LaunchCallbackFaultedTest() { var testReporter = BuildTestReporter(); var t = Task.FromException(new Exception("test")); testReporter.LaunchCallback(t); // verify that we did report the launch proble _mainLog.Verify(l => l.WriteLine( It.Is(s => s.StartsWith($"Test execution failed:"))), Times.Once); } [Fact] public void LaunchCallbackCanceledTest() { var testReporter = BuildTestReporter(); var tcs = new TaskCompletionSource(); tcs.TrySetCanceled(); testReporter.LaunchCallback(tcs.Task); // verify we notify that the execution was canceled _mainLog.Verify(l => l.WriteLine(It.Is(s => s.Equals("Test execution was cancelled"))), Times.Once); } [Fact] public void LaunchCallbackSuccessTest() { var testReporter = BuildTestReporter(); var t = Task.FromResult(true); testReporter.LaunchCallback(t); _mainLog.Verify(l => l.WriteLine(It.Is(s => s.Equals("Test execution started"))), Times.Once); } [Fact] public void LaunchCallbackTimedOutTest() { var tcs = new TaskCompletionSource(); _listener.Setup(l => l.ConnectedTask).Returns(Task.FromResult(true)); var testReporter = BuildTestReporter(); var t = Task.FromResult(false); testReporter.LaunchCallback(t); _mainLog.Verify(l => l.WriteLine(It.Is(s => s.Contains("Test execution timed out"))), Times.Once); } [Fact] public void LaunchCallbackLaunchTimedOutTest() { var tcs = new TaskCompletionSource(); _listener.Setup(l => l.ConnectedTask).Returns(Task.FromResult(false)); var testReporter = BuildTestReporter(); var t = Task.FromResult(false); testReporter.LaunchCallback(t); _mainLog.Verify(l => l.WriteLine(It.Is(s => s.Contains("Test failed to start"))), Times.Once); } // copy the sample data to a given tmp file private string CreateSampleFile(string resourceName) { var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith(resourceName, StringComparison.Ordinal)).FirstOrDefault(); var tempPath = Path.GetTempFileName(); using var outputStream = new StreamWriter(tempPath); using var sampleStream = new StreamReader(GetType().Assembly.GetManifestResourceStream(name)); string line; while ((line = sampleStream.ReadLine()) != null) { outputStream.WriteLine(line); } return tempPath; } [Fact] public async Task ParseResultFailingTestsTest() { var sample = CreateSampleFile("NUnitV3SampleFailure.xml"); var listenerLog = Mock.Of(l => l.FullPath == sample); _listener.Setup(l => l.TestLog).Returns(listenerLog); var testReporter = BuildTestReporter(); var (result, resultMessage) = await testReporter.ParseResult(); Assert.Equal(TestExecutingResult.Failed, result); Assert.Equal("Tests run: 5 Passed: 3 Inconclusive: 1 Failed: 2 Ignored: 4", resultMessage); // ensure that we do call the crash reporter end capture but with 0, since it was a success _crashReporter.Verify(c => c.EndCaptureAsync(TimeSpan.FromSeconds(5)), Times.Once); } [Fact] public async Task ParseResultSuccessTestsTest() { // get a file with a success result so that we can return it as part of the listener log var sample = CreateSampleFile("NUnitV3SampleSuccess.xml"); var listenerLog = Mock.Of(l => l.FullPath == sample); _listener.Setup(l => l.TestLog).Returns(listenerLog); var testReporter = BuildTestReporter(); var (result, resultMessage) = await testReporter.ParseResult(); Assert.Equal(TestExecutingResult.Succeeded, result); Assert.Equal("Tests run: 5 Passed: 4 Inconclusive: 0 Failed: 0 Ignored: 1", resultMessage); // ensure that we do call the crash reporter end capture but with 0, since it was a success _crashReporter.Verify(c => c.EndCaptureAsync(It.Is(t => t.TotalSeconds == 0)), Times.Once); } [Fact] public async Task ParseResultTimeoutTestsTest() { // more complicated test, we need to fake a process timeout, then ensure that the result is the expected one var tcs = new TaskCompletionSource(); _listener.Setup(l => l.CompletionTask).Returns(tcs.Task); // will never be set to be completed var listenerLog = new Mock(); _listener.Setup(l => l.TestLog).Returns(listenerLog.Object); listenerLog.Setup(l => l.FullPath).Returns("/my/missing/path"); // ensure that we do provide the required runlog information so that we know if it was a launch failure or not, we are // not dealing with the launch faliure _runLog.Setup(l => l.GetReader()).Returns(new StreamReader(GetRunLogSample())); var failurePath = Path.Combine(_logsDirectory, "my-failure.xml"); var failureLog = new Mock(); failureLog.Setup(l => l.FullPath).Returns(failurePath); _logs.Setup(l => l.Create(It.IsAny(), It.IsAny(), null)).Returns(failureLog.Object); // create some data for the stderr var stderr = Path.GetTempFileName(); using (var stream = File.Create(stderr)) using (var writer = new StreamWriter(stream)) { await writer.WriteAsync("Some data to be added to stderr of the failure"); } _mainLog.Setup(l => l.FullPath).Returns(stderr); var testReporter = BuildTestReporter(); var processResult = new ProcessExecutionResult() { TimedOut = true, ExitCode = 0 }; await testReporter.CollectDeviceResult(processResult); // we should have timeout, since the task completion source was never set var (result, failure) = await testReporter.ParseResult(); Assert.False(testReporter.Success, "success"); // verify that we state that there was a timeout _mainLog.Verify(l => l.WriteLine(It.Is(s => s.Equals("Test run never launched"))), Times.Once); // assert that the timeout failure was created. Assert.True(File.Exists(failurePath), "failure path"); var isTimeoutFailure = false; using (var reader = new StreamReader(failurePath)) { string line = null; while ((line = await reader.ReadLineAsync()) != null) { if (line.Contains("App Timeout")) { isTimeoutFailure = true; break; } } } Assert.True(isTimeoutFailure, "correct xml"); File.Delete(failurePath); } // it is possible that the app didn't connect in time in which case we should receive the LaunchTimedOut result [Fact] public async Task ParseResultLaunchTimedOutTest() { // set the listener to return a task that we are not going to complete var cancellationTokenSource = new CancellationTokenSource(); var tcs = new TaskCompletionSource(); _listener.Setup(l => l.ConnectedTask).Returns(tcs.Task); // will never be set to be completed var listenerLog = Mock.Of(l => l.FullPath == "/this/path/does/not/exist"); _listener.Setup(l => l.TestLog).Returns(listenerLog); var failurePath = Path.Combine(_logsDirectory, "my-failure.xml"); var failureLog = new Mock(); failureLog.Setup(l => l.FullPath).Returns(failurePath); _logs.Setup(l => l.Create(It.IsAny(), It.IsAny(), null)).Returns(failureLog.Object); // create some data for the stderr var stderr = Path.GetTempFileName(); using (var stream = File.Create(stderr)) using (var writer = new StreamWriter(stream)) { await writer.WriteAsync("Some data to be added to stderr of the failure"); } _mainLog.Setup(l => l.FullPath).Returns(stderr); var testReporter = BuildTestReporter(); // this is called when launch timeout expires // false means we never received a connection which would flip it to true testReporter.LaunchCallback(Task.FromResult(false)); var (result, resultMessage) = await testReporter.ParseResult(); Assert.Equal(TestExecutingResult.LaunchTimedOut, result); // verify that we do not try to kill a process that never got started _processManager.Verify(p => p.KillTreeAsync(It.IsAny(), It.IsAny(), true), Times.Never); _crashReporter.Verify(c => c.EndCaptureAsync(TimeSpan.FromSeconds(5)), Times.Once); } /// /// When CollectDeviceResult sets Success=true (tests completed via app end signal) /// but the results file copy fails (e.g., devicectl error on tvOS), ParseResult should /// return Succeeded rather than Crashed, since we already confirmed test completion. /// [Fact] public async Task ParseResult_WhenTestCompletedButResultsUnavailable_ReturnsSucceeded() { // Set up listener with a non-existent log file (simulate failed devicectl copy) var listenerLog = Mock.Of(l => l.FullPath == "/this/path/does/not/exist"); _listener.Setup(l => l.TestLog).Returns(listenerLog); var testReporter = BuildTestReporter(); // Simulate mlaunch exiting with 0 (after app end signal detection → RunAndWatchForAppSignal sets ExitCode=0) var processResult = new ProcessExecutionResult() { TimedOut = false, ExitCode = 0 }; await testReporter.CollectDeviceResult(processResult); // At this point Success=true (set by CollectDeviceResult via CollectResult) Assert.True(testReporter.Success, "Success should be true after CollectDeviceResult with ExitCode=0"); var (result, resultMessage) = await testReporter.ParseResult(); // Should return Succeeded, not Crashed, since we know tests completed Assert.Equal(TestExecutingResult.Succeeded, result); Assert.True(testReporter.Success, "Success should remain true"); Assert.Contains("completed but results file was not available", resultMessage); _mainLog.Verify(l => l.WriteLine(It.Is(s => s.Contains("Test run completed but results file was not available"))), Times.Once); // Crash reporter should be called with 0 delay since tests succeeded _crashReporter.Verify(c => c.EndCaptureAsync(It.Is(t => t.TotalSeconds == 0)), Times.Once); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Utilities/PListExtensionsTests.cs ================================================ using System; using System.IO; using System.Linq; using System.Text; using System.Xml; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Utilities; public class PListExtensionsTests { private readonly XmlDocument _plist; public PListExtensionsTests() => _plist = CreateResultSample(); /// /// Creates a sample pList to be used with the tests and returns the temp file in which it was stored. /// /// The path where the sample plist can be found. private XmlDocument CreateResultSample() { var name = GetType().Assembly.GetManifestResourceNames() .FirstOrDefault(a => a.EndsWith("Info.plist", StringComparison.Ordinal)); var tempPath = Path.GetTempFileName(); var byteOrderMarkUtf8 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); // I hate BOM using var sampleStream = new StreamReader(GetType().Assembly.GetManifestResourceStream(name)); // create the document with the plist and return it var doc = new XmlDocument(); var settings = new XmlReaderSettings() { XmlResolver = null, DtdProcessing = DtdProcessing.Parse, }; using var reader = XmlReader.Create(sampleStream, settings); doc.Load(reader); return doc; } [Fact] public void SetMinimumOSVersion() { var version = "MyMinVersion"; _plist.SetMinimumOSVersion(version); Assert.Equal(version, _plist.GetMinimumOSVersion()); } [Fact] public void SetNullMinimumOSVersion() => Assert.Throws(() => _plist.SetMinimumOSVersion(null)); [Fact] public void SetMinimummacOSVersion() { var version = "MyMaccMinVersion"; _plist.SetMinimummacOSVersion(version); Assert.Equal(version, _plist.GetMinimummacOSVersion()); } [Fact] public void SetNullMinimummacOSVersion() => Assert.Throws(() => _plist.SetMinimummacOSVersion(null)); [Fact] public void SetCFBundleDisplayName() { var displayName = "MySuperApp"; _plist.SetCFBundleDisplayName(displayName); Assert.Equal(displayName, _plist.GetCFBundleDisplayName()); } [Fact] public void SetNullCFBundleDisplayName() => Assert.Throws(() => _plist.SetCFBundleDisplayName(null)); [Fact] public void SetCFBundleIdentifier() { var bundleIdentifier = "my.company.super.app"; _plist.SetCFBundleIdentifier(bundleIdentifier); Assert.Equal(bundleIdentifier, _plist.GetCFBundleIdentifier()); } [Fact] public void SetNullCFBundleIdentifier() => Assert.Throws(() => _plist.SetCFBundleIdentifier(null)); [Fact] public void SetCFBundleName() { var bundleName = "MySuper.app"; _plist.SetCFBundleName(bundleName); Assert.Equal(bundleName, _plist.GetCFBundleName()); } [Fact] public void SetNullCFBundleName() => Assert.Throws(() => _plist.SetCFBundleName(null)); } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/Utilities/ProjectFileExtensionsTests.cs ================================================ using System; using System.IO; using System.Linq; using System.Xml; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests.Utilities; public class ProjectFileExtensionsTests { private static XmlDocument CreateDoc(string xml) { var doc = new XmlDocument(); doc.LoadXmlWithoutNetworkAccess(xml); return doc; } private static XmlDocument GetMSBuildProject(string snippet) { return CreateDoc($@" {snippet} "); } [Fact] public void GetInfoPListNode() { // Exact Include Assert.NotNull(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject(""))); Assert.NotNull(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject(""))); Assert.NotNull(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject(""))); Assert.Null(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject(""))); // With LogicalName Assert.NotNull(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject("Info.plist"))); Assert.NotNull(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject("Info.plist"))); Assert.NotNull(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject("Info.plist"))); Assert.Null(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject("Info.plist"))); // With Link Assert.NotNull(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject("Info.plist"))); Assert.NotNull(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject("Info.plist"))); Assert.NotNull(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject("Info.plist"))); Assert.Null(ProjectFileExtensions.GetInfoPListNode(GetMSBuildProject("Info.plist"))); } [Fact] public void MtouchArchPropertyIsDetected() { var assembly = GetType().Assembly; var name = assembly.GetManifestResourceNames().Where(a => a.EndsWith("MtouchArchMissingInConfiguration.xml", StringComparison.Ordinal)).First(); var reader = new StreamReader(assembly.GetManifestResourceStream(name)!); var csproj = CreateDoc(reader.ReadToEnd()); var arch = csproj.GetMtouchArch("iPhone", "Release64"); Assert.Equal("ARM64", arch); } [Fact] public void MissingMtouchArchPropertyInConfigurationIsHandled() { var assembly = GetType().Assembly; var name = assembly.GetManifestResourceNames().Where(a => a.EndsWith("MtouchArchMissingInConfiguration.xml", StringComparison.Ordinal)).First(); var reader = new StreamReader(assembly.GetManifestResourceStream(name)!); var csproj = CreateDoc(reader.ReadToEnd()); var arch = csproj.GetMtouchArch("iPhoneSimulator", "Debug"); Assert.Null(arch); } [Fact] public void MissingMtouchArchPropertyInCsprojIsHandled() { var assembly = GetType().Assembly; var name = assembly.GetManifestResourceNames().Where(a => a.EndsWith("MtouchArchMissingEverywhere.xml", StringComparison.Ordinal)).First(); var reader = new StreamReader(assembly.GetManifestResourceStream(name)!); var csproj = CreateDoc(reader.ReadToEnd()); var arch = csproj.GetMtouchArch("iPhoneSimulator", "Debug"); Assert.Null(arch); } } ================================================ FILE: tests/Microsoft.DotNet.XHarness.iOS.Shared.Tests/XmlResultParserTests.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.Logging; using Microsoft.DotNet.XHarness.iOS.Shared.XmlResults; using Moq; using Xunit; namespace Microsoft.DotNet.XHarness.iOS.Shared.Tests; public class XmlResultParserTests { private static readonly Dictionary> s_validationMap = new() { [XmlResultJargon.NUnitV2] = ValidateNUnitV2Failure, [XmlResultJargon.NUnitV3] = ValidateNUnitV3Failure, [XmlResultJargon.xUnit] = ValidatexUnitFailure, }; private readonly XmlResultParser _resultParser; public XmlResultParserTests() { _resultParser = new XmlResultParser(); } private string CreateResultSample(XmlResultJargon jargon, bool includePing = false) { string sampleFileName = null; switch (jargon) { case XmlResultJargon.NUnitV2: sampleFileName = "NUnitV2Sample.xml"; break; case XmlResultJargon.NUnitV3: sampleFileName = "NUnitV3Sample.xml"; break; case XmlResultJargon.TouchUnit_NUnitV2: sampleFileName = "TouchUnitSample.xml"; break; case XmlResultJargon.TouchUnit_NUnitV3: sampleFileName = "TouchUnitSample2.xml"; break; case XmlResultJargon.xUnit: sampleFileName = "xUnitSample.xml"; break; } Assert.NotNull(sampleFileName); var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith(sampleFileName, StringComparison.Ordinal)).FirstOrDefault(); var tempPath = Path.GetTempFileName(); using (var outputStream = new StreamWriter(tempPath)) using (var sampleStream = new StreamReader(GetType().Assembly.GetManifestResourceStream(name))) { if (includePing) { outputStream.WriteLine("ping"); } string line; while ((line = sampleStream.ReadLine()) != null) { outputStream.WriteLine(line); } } return tempPath; } [Fact] public void IsValidXmlMissingFileTest() { var path = Path.GetTempFileName(); File.Delete(path); Assert.False(_resultParser.IsValidXml(path, out _), "missing file"); } [Theory] [InlineData(XmlResultJargon.NUnitV2)] [InlineData(XmlResultJargon.NUnitV3)] [InlineData(XmlResultJargon.TouchUnit_NUnitV2)] [InlineData(XmlResultJargon.TouchUnit_NUnitV3)] [InlineData(XmlResultJargon.xUnit)] public void IsValidXmlTest(XmlResultJargon jargon) { var path = CreateResultSample(jargon); Assert.True(_resultParser.IsValidXml(path, out var resultJargon), "is valid"); Assert.Equal(jargon, resultJargon); File.Delete(path); } [Theory] [InlineData("nunit-", XmlResultJargon.NUnitV2)] [InlineData("nunit-", XmlResultJargon.TouchUnit)] [InlineData("xunit-", XmlResultJargon.xUnit)] public void GetXmlFilePathTest(string prefix, XmlResultJargon jargon) { var orignialPath = "/path/to/a/xml/result.xml"; var xmlPath = _resultParser.GetXmlFilePath(orignialPath, jargon); var fileName = Path.GetFileName(xmlPath); Assert.StartsWith(prefix, fileName); } [Theory] [InlineData(XmlResultJargon.NUnitV3)] [InlineData(XmlResultJargon.NUnitV2)] [InlineData(XmlResultJargon.xUnit)] public void CleanXmlPingTest(XmlResultJargon jargon) { var path = CreateResultSample(jargon, includePing: true); var cleanPath = path + "_clean"; _resultParser.CleanXml(path, cleanPath); Assert.True(_resultParser.IsValidXml(cleanPath, out var resultJargon), "is valid"); Assert.Equal(jargon, resultJargon); File.Delete(path); File.Delete(cleanPath); } [Fact] public void CleanXmlTouchUnitTest() { // similar to CleanXmlPingTest but using TouchUnit, so we do not want to see the extra nodes var path = CreateResultSample(XmlResultJargon.TouchUnit, includePing: true); var cleanPath = path + "_clean"; _resultParser.CleanXml(path, cleanPath); Assert.True(_resultParser.IsValidXml(cleanPath, out var resultJargon), "is valid"); Assert.Equal(XmlResultJargon.NUnitV2, resultJargon); // load the xml, ensure we do not have the nodes we removed var doc = XDocument.Load(cleanPath); Assert.False(doc.Descendants().Where(e => e.Name == "TouchUnitTestRun").Any(), "TouchUnitTestRun"); Assert.False(doc.Descendants().Where(e => e.Name == "NUnitOutput").Any(), "NUnitOutput"); File.Delete(path); File.Delete(cleanPath); } [Fact] public void UpdateMissingDataTest() // only works with NUnitV3 { string appName = "TestApp"; var path = CreateResultSample(XmlResultJargon.NUnitV3); var cleanPath = path + "_clean"; _resultParser.CleanXml(path, cleanPath); var updatedXml = path + "_updated"; var logs = new[] { "/first/path", "/second/path", "/last/path" }; _resultParser.UpdateMissingData(cleanPath, updatedXml, appName, logs); // assert that the required info was updated Assert.True(File.Exists(updatedXml), "file exists"); var doc = XDocument.Load(updatedXml); var testSuiteElements = doc.Descendants().Where(e => e.Name == "test-suite" && e.Attribute("type")?.Value == "Assembly"); // assert root node contains the attachments var rootNode = testSuiteElements.FirstOrDefault(); Assert.NotNull(rootNode); var attachments = rootNode.Descendants().Where(e => e.Name == "attachment"); var failureCount = rootNode.Descendants().Where(e => e.Name == "test-case" && e.Attribute("result").Value == "Failed").Count(); Assert.Equal(logs.Length * (failureCount + 1), attachments.Count()); // assert that name and full name are present and are the app name foreach (var node in testSuiteElements) { Assert.Equal(appName, node.Attribute("name").Value); Assert.Equal(appName, node.Attribute("fullname").Value); } File.Delete(path); File.Delete(cleanPath); File.Delete(updatedXml); } [Fact] public void GetVSTSFileNameTest() { var path = Path.GetTempFileName(); var newPath = XmlResultParser.GetVSTSFilename(path); Assert.StartsWith("vsts-", Path.GetFileName(newPath)); File.Delete(path); } private static void ValidateNUnitV2Failure(string src, string appName, string variation, string title, string message, string stderrMessage, string xmlPath, int _) { // load the doc and ensure that all the data is correct setup var doc = XDocument.Load(xmlPath); var testResultsNodes = doc.Descendants().Where(e => e.Name == "test-results"); Assert.Single(testResultsNodes); var rootNode = testResultsNodes.FirstOrDefault(); Assert.Equal(title, rootNode.Attribute("name").Value); Assert.Equal("1", rootNode.Attribute("total").Value); Assert.Equal("0", rootNode.Attribute("errors").Value); Assert.Equal("1", rootNode.Attribute("failures").Value); // ensure we do have a test result with the failure data var testResult = doc.Descendants().Where(e => e.Name == "test-suite" && e.Attribute("type").Value == "TestFixture"); Assert.Single(testResult); } private static void ValidateNUnitV3Failure(string src, string appName, string variation, string title, string message, string stderrMessage, string xmlPath, int attachemntsCount) { var doc = XDocument.Load(xmlPath); // get test-run and verify attrs var testResultNodes = doc.Descendants().Where(e => e.Name == "test-run"); Assert.Single(testResultNodes); var testResultNode = testResultNodes.FirstOrDefault(); Assert.Equal(title, testResultNode.Attribute("name").Value); Assert.Equal("1", testResultNode.Attribute("testcasecount").Value); Assert.Equal("Failed", testResultNode.Attribute("result").Value); Assert.Equal("1", testResultNode.Attribute("total").Value); Assert.Equal("0", testResultNode.Attribute("passed").Value); Assert.Equal("1", testResultNode.Attribute("failed").Value); Assert.Equal("1", testResultNode.Attribute("asserts").Value); // important attrs for the import, if they miss, we wont be able to add the files to vsts Assert.NotNull(testResultNode.Attribute("run-date").Value); Assert.NotNull(testResultNode.Attribute("start-time").Value); // get the test-suite and verify the name and fullname are correct var testSuite = testResultNode.Descendants().Where(e => e.Name == "test-suite" && e.Attribute("type").Value == "TestFixture").FirstOrDefault(); Assert.NotNull(testSuite); Assert.Equal(title, testSuite.Attribute("name").Value); Assert.Equal(title, testSuite.Attribute("fullname").Value); // verify the test case var testCase = testSuite.Descendants().Where(e => e.Name == "test-case").FirstOrDefault(); Assert.NotNull(testCase); Assert.Equal("Failed", testCase.Attribute("result").Value); // validate that we do have attachments var attachmentsNode = testCase.Descendants().Where(e => e.Name == "attachments").FirstOrDefault(); Assert.NotNull(attachmentsNode); var attachments = attachmentsNode.Descendants().Where(e => e.Name == "attachment"); Assert.Equal(attachemntsCount, attachments.Count()); } private static void ValidatexUnitFailure(string src, string appName, string variation, string title, string message, string stderrMessage, string xmlPath, int _) { var doc = XDocument.Load(xmlPath); // get the assemlby and validate its attrs var assemblies = doc.Descendants().Where(e => e.Name == "assembly"); Assert.Single(assemblies); var assemblyNode = assemblies.FirstOrDefault(); Assert.Equal(title, assemblyNode.Attribute("name").Value); Assert.Equal("1", assemblyNode.Attribute("total").Value); Assert.Equal("1", assemblyNode.Attribute("failed").Value); Assert.Equal("0", assemblyNode.Attribute("passed").Value); var collections = assemblyNode.Descendants().Where(e => e.Name == "collection"); Assert.Single(collections); var collectionNode = collections.FirstOrDefault(); // assert the collection attrs Assert.Equal("1", collectionNode.Attribute("failed").Value); Assert.Equal("0", collectionNode.Attribute("passed").Value); } [Theory] [InlineData(XmlResultJargon.NUnitV2)] [InlineData(XmlResultJargon.NUnitV3)] [InlineData(XmlResultJargon.xUnit)] public void GenerateFailureTest(XmlResultJargon jargon) { var src = "test-case"; var appName = "MyUnitTest"; var variation = "Debug"; var title = "Testing"; var message = "This is a test"; var stderrMessage = "Something went very wrong"; var stderrPath = Path.GetTempFileName(); // write the message in the stderrParh that should be read using (var writer = new StreamWriter(stderrPath)) { writer.WriteLine(stderrMessage); } // create a path with data in it var logs = new Mock(); var tmpLogMock = new Mock(); var xmlLogMock = new Mock(); var tmpPath = Path.GetTempFileName(); var finalPath = Path.GetTempFileName(); // create a number of fake logs to be added to the failure var logsDir = Path.GetTempFileName(); File.Delete(logsDir); Directory.CreateDirectory(logsDir); var failureLogs = new[] { "first.txt", "second.txt", "last.txt" }; foreach (var file in failureLogs) { var path = Path.Combine(logsDir, file); File.WriteAllText(path, ""); } // expect the creation of the two diff xml file logs _ = logs.Setup(l => l.Create(It.IsAny(), "Failure Log tmp", null)).Returns(tmpLogMock.Object); _ = logs.Setup(l => l.Create(It.IsAny(), LogType.XmlLog.ToString(), null)).Returns(xmlLogMock.Object); if (jargon == XmlResultJargon.NUnitV3) { _ = logs.Setup(l => l.Directory).Returns(logsDir); _ = tmpLogMock.Setup(tmpLog => tmpLog.FullPath).Returns(tmpPath); } // return the two temp files so that we can later validate that everything is present _ = xmlLogMock.Setup(xmlLog => xmlLog.FullPath).Returns(finalPath); _resultParser.GenerateFailure(logs.Object, src, appName, variation, title, message, stderrPath, jargon); // actual assertions do happen in the validation functions s_validationMap[jargon](src, appName, variation, title, message, stderrMessage, finalPath, failureLogs.Length); // verify that we are correctly adding the logs logs.Verify(l => l.Create(It.IsAny(), It.IsAny(), null), jargon == XmlResultJargon.NUnitV3 ? Times.AtMost(2) : Times.AtMostOnce()); if (jargon == XmlResultJargon.NUnitV3) { logs.Verify(l => l.Directory, Times.Once); tmpLogMock.Verify(l => l.FullPath, Times.AtLeastOnce); } xmlLogMock.Verify(l => l.FullPath, Times.AtLeastOnce); // clean files File.Delete(stderrPath); File.Delete(tmpPath); File.Delete(finalPath); Directory.Delete(logsDir, true); } /// /// https://github.com/xamarin/xamarin-macios/issues/8214 /// [Fact] public void Issue8214Test() { string expectedResultLine = "Tests run: 2376 Passed: 2301 Inconclusive: 13 Failed: 1 Ignored: 74"; // get the sample that was added to the issue to validate that we do parse the resuls correctly and copy it to a local // path to be parsed var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith("Issue8214.xml", StringComparison.Ordinal)).FirstOrDefault(); var tempPath = Path.GetTempFileName(); var destinationFile = Path.GetTempFileName(); using (var outputStream = new StreamWriter(tempPath)) using (var sampleStream = new StreamReader(GetType().Assembly.GetManifestResourceStream(name))) { string line; while ((line = sampleStream.ReadLine()) != null) { outputStream.WriteLine(line); } } var (resultLine, failed) = _resultParser.ParseResults(tempPath, XmlResultJargon.NUnitV3, destinationFile); Assert.True(failed, "failed"); Assert.Equal(expectedResultLine, resultLine); // verify that the destination does contain the result line string resultLineInDestinationFile = null; using (var resultReader = new StreamReader(destinationFile)) { string line; while ((line = resultReader.ReadLine()) != null) { if (line.Contains("Tests run:")) { resultLineInDestinationFile = line; break; } } } Assert.NotNull(resultLineInDestinationFile); Assert.Equal(expectedResultLine, resultLineInDestinationFile); } [Theory] [InlineData("Issue8214.xml", true, "Tests run: 2376 Passed: 2301 Inconclusive: 13 Failed: 1 Ignored: 74")] // https://github.com/xamarin/xamarin-macios/issues/8214 [InlineData("NUnitV2Sample.xml", true, "Tests run: 21 Passed: 4 Inconclusive: 1 Failed: 2 Ignored: 7")] [InlineData("NUnitV2SampleFailure.xml", true, "Tests run: 21 Passed: 4 Inconclusive: 1 Failed: 2 Ignored: 7")] [InlineData("NUnitV3Sample.xml", true, "Tests run: 25 Passed: 12 Inconclusive: 1 Failed: 2 Ignored: 4")] [InlineData("NUnitV3SampleFailure.xml", true, "Tests run: 5 Passed: 3 Inconclusive: 1 Failed: 2 Ignored: 4")] [InlineData("TestCaseFailures.xml", true, "Tests run: 440 Passed: 405 Inconclusive: 0 Failed: 23 Ignored: 6")] [InlineData("TouchUnitSample.xml", false, "Tests run: 2354 Passed: 2223 Inconclusive: 13 Failed: 0 Ignored: 59")] // The counting is a bit off here, seems like that's in Touch.Unit [InlineData("xUnitSample.xml", false, "Tests run: 53821 Passed: 53801 Inconclusive: 0 Failed: 0 Ignored: 20")] [InlineData("NUnitV3SampleParameterizedFailure.xml", true, "Tests run: 2086 Passed: 2041 Inconclusive: 7 Failed: 2 Ignored: 43")] public void DoNotGenerateHtmlReport(string xmlFile, bool expectedFailure, string expectedResultLine) { // get the sample xml to parse var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith(xmlFile, StringComparison.Ordinal)).FirstOrDefault(); using var validXmlSource = new StreamReader(GetType().Assembly.GetManifestResourceStream(name)); using var source = new StreamReader(GetType().Assembly.GetManifestResourceStream(name)); var tempPath = Path.GetTempFileName(); using (var outputStream = new StreamWriter(tempPath)) using (var sampleStream = new StreamReader(GetType().Assembly.GetManifestResourceStream(name))) { string line; while ((line = sampleStream.ReadLine()) != null) { outputStream.WriteLine(line); } } // Get the xml type Assert.True(_resultParser.IsValidXml(validXmlSource, out var type)); // generate the results var (resultLine, failed) = _resultParser.ParseResults(tempPath, type, humanReadableReportDestination: (StreamWriter)null); Assert.Equal(expectedFailure, failed); Assert.Equal(expectedResultLine, resultLine); } [Fact] public void Issue91Test() // make sure that the skipped value is correct according to the provided xml in the issue. { string expectedResultLine = "Tests run: 3 Passed: 1 Inconclusive: 0 Failed: 1 Ignored: 1"; var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith("Issue95.xml", StringComparison.Ordinal)).FirstOrDefault(); var tempPath = Path.GetTempFileName(); using (var outputStream = new StreamWriter(tempPath)) using (var sampleStream = new StreamReader(GetType().Assembly.GetManifestResourceStream(name))) { string line; while ((line = sampleStream.ReadLine()) != null) { outputStream.WriteLine(line); } } var (resultLine, failed) = _resultParser.ParseResults(tempPath, XmlResultJargon.NUnitV2, (StreamWriter)null); Assert.True(failed, "failed"); Assert.Equal(expectedResultLine, resultLine); } [Theory] [InlineData("Issue8214.xml", true, "Tests run: 2376 Passed: 2301 Inconclusive: 13 Failed: 1 Ignored: 74")] // https://github.com/xamarin/xamarin-macios/issues/8214 [InlineData("NUnitV2Sample.xml", true, "Tests run: 21 Passed: 4 Inconclusive: 1 Failed: 2 Ignored: 7")] [InlineData("NUnitV2SampleFailure.xml", true, "Tests run: 21 Passed: 4 Inconclusive: 1 Failed: 2 Ignored: 7")] [InlineData("NUnitV3Sample.xml", true, "Tests run: 25 Passed: 12 Inconclusive: 1 Failed: 2 Ignored: 4")] [InlineData("NUnitV3SampleFailure.xml", true, "Tests run: 5 Passed: 3 Inconclusive: 1 Failed: 2 Ignored: 4")] [InlineData("TestCaseFailures.xml", true, "Tests run: 440 Passed: 405 Inconclusive: 0 Failed: 23 Ignored: 6")] [InlineData("TouchUnitSample.xml", false, "Tests run: 2354 Passed: 2223 Inconclusive: 13 Failed: 0 Ignored: 59", new string[] { "Tests run: 2286 Passed: 2282 Inconclusive: 4 Failed: 0 Ignored: 47" })] // The counting is a bit off here, seems like that's in Touch.Unit [InlineData("xUnitSample.xml", false, "Tests run: 53821 Passed: 53801 Inconclusive: 0 Failed: 0 Ignored: 20")] [InlineData("NUnitV3SampleParameterizedFailure.xml", true, "Tests run: 2086 Passed: 2041 Inconclusive: 7 Failed: 2 Ignored: 43", new string[] { " [FAIL] GHIssue8342(OK,\"mandel\",\"12345678\",\"mandel\",\"12345678\") : Status not ok" })] public void HumanReadableResultsTest(string xmlFile, bool expectedFailure, string expectedResultLine, string[] additionalLines = null) { // get the sample xml to parse var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith(xmlFile, StringComparison.Ordinal)).FirstOrDefault(); using var validXmlSource = new StreamReader(GetType().Assembly.GetManifestResourceStream(name)); using var source = new StreamReader(GetType().Assembly.GetManifestResourceStream(name)); using var memoryStream = new MemoryStream(); using var destination = new StreamWriter(memoryStream); var tempPath = Path.GetTempFileName(); using (var outputStream = new StreamWriter(tempPath)) using (var sampleStream = new StreamReader(GetType().Assembly.GetManifestResourceStream(name))) { string line; while ((line = sampleStream.ReadLine()) != null) { outputStream.WriteLine(line); } } // Get the xml type Assert.True(_resultParser.IsValidXml(validXmlSource, out var type)); // generate the results var (resultLine, failed) = _resultParser.ParseResults(tempPath, type, destination); destination.Flush(); memoryStream.Position = 0; using var reader = new StreamReader(memoryStream); var output = reader.ReadToEnd(); Assert.Equal(expectedFailure, failed); Assert.Equal(expectedResultLine, resultLine); if (additionalLines != null) { var lines = output.Split('\n'); foreach (var line in additionalLines) { Assert.Contains(line, lines); } } if (expectedFailure) { Assert.Contains("[FAIL]", output); } else { Assert.DoesNotContain("[FAIL]", output); } } [Fact] public void NUnitV2GenerateTestReport() { var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith("NUnitV2SampleFailure.xml", StringComparison.Ordinal)).FirstOrDefault(); using var writer = new StringWriter(); using var stream = GetType().Assembly.GetManifestResourceStream(name); using var reader = new StreamReader(stream); _resultParser.GenerateTestReport(writer, reader, XmlResultJargon.NUnitV2); var expectedOutput = @"
  • ErrorTest1:
    " + "Multiline
    \nerror
    \nmessage
    " + @"
  • NUnit.Tests.Assemblies.MockTestFixture.FailingTest: Intentional failure
  • NUnit.Tests.Assemblies.MockTestFixture.TestWithException: System.ApplicationException : Intentional Exception
"; Assert.Equal(expectedOutput, writer.ToString()); } [Fact] public void NUnitV2GenerateTestReportWithInlineDataFailures() { var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith("TestCaseFailures.xml", StringComparison.Ordinal)).FirstOrDefault(); using var writer = new StringWriter(); using var stream = GetType().Assembly.GetManifestResourceStream(name); using var reader = new StreamReader(stream); _resultParser.GenerateTestReport(writer, reader, XmlResultJargon.NUnitV2); var expectedOutput = @"
  • Xamarin.MTouch.FastDev_LinkAll(iOS): message
  • Xamarin.MTouch.RebuildWhenReferenceSymbolsInCode: message
"; Assert.Equal(writer.ToString(), expectedOutput); } [Fact] public void NUnitV3MultipleTestFailures() { var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith("NUnitV3SampleFailures.xml", StringComparison.Ordinal)).FirstOrDefault(); using var writer = new StringWriter(); using var stream = GetType().Assembly.GetManifestResourceStream(name); using var reader = new StreamReader(stream); _resultParser.GenerateTestReport(writer, reader, XmlResultJargon.NUnitV3); var expectedOutput = @"
  • MonoTouchFixtures.Foundation.TimerTest.Bug17793: Not signalled twice in 5s
    Expected: True
    But was: False

  • MonoTouchFixtures.Foundation.TimerTest.Bug2443: Not signalled twice in 5s
    Expected: True
    But was: False

  • MonoTouchFixtures.Foundation.TimerTest.CreateTimer_NewSignature: WaitOne
    Expected: True
    But was: False

"; Assert.Equal(writer.ToString().Replace("\r", ""), expectedOutput.Replace("\r", "")); } [Fact] public void TouchUnitTestReport() { var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith("TouchUnitSample.xml", StringComparison.Ordinal)).FirstOrDefault(); using var writer = new StringWriter(); using var stream = GetType().Assembly.GetManifestResourceStream(name); using var reader = new StreamReader(stream); _resultParser.GenerateTestReport(writer, reader, XmlResultJargon.TouchUnit_NUnitV2); var expectedOutput = ""; Assert.Equal(expectedOutput.Replace("\r", ""), writer.ToString().Replace("\r", "")); } [Fact] public void TouchUnit2TestReport() { var name = GetType().Assembly.GetManifestResourceNames().Where(a => a.EndsWith("TouchUnitSample2.xml", StringComparison.Ordinal)).FirstOrDefault(); using var writer = new StringWriter(); using var stream = GetType().Assembly.GetManifestResourceStream(name); using var reader = new StreamReader(stream); _resultParser.GenerateTestReport(writer, reader, XmlResultJargon.TouchUnit_NUnitV3); var expectedOutput = @"
  • MonoTests.System.Net.Http.MessageHandlerTest.TestNSUrlSessionDefaultDisableCookiesWithManagedContainer: Network request completed
    Expected: True
    But was: False

  • MonoTests.System.Net.Http.MessageHandlerTest.TestNSUrlSessionDefaultDisabledCookies: Network request completed
    Expected: True
    But was: False

  • MonoTests.System.Net.Http.MessageHandlerTest.TestNSUrlSessionHandlerCookies: Network request completed
    Expected: True
    But was: False

  • MonoTests.System.Net.Http.MessageHandlerTest.UpdateRequestUriAfterRedirect(System.Net.Http.NSUrlSessionHandler): Post RequestUri
    Expected string length 20 but was 64. Strings differ at index 20.
    Expected: "https://httpbin.org/"
    But was: "https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2F"
    -------------------------------^

  • MonoTests.System.Net.Http.MessageHandlerTest.UpdateRequestUriAfterRedirect(System.Net.Http.SocketsHttpHandler): Post RequestUri
    Expected string length 20 but was 64. Strings differ at index 20.
    Expected: "https://httpbin.org/"
    But was: "https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2F"
    -------------------------------^

"; Assert.Equal(expectedOutput.Replace("\r", ""), writer.ToString().Replace("\r", "")); } } ================================================ FILE: tests/integration-tests/Android/Commands.Tests.proj ================================================ System.Numerics.Vectors.Tests $(AssetsBaseUri)/android/test-apk/x86/$(TestPackageName)-x86.zip $(ArtifactsTmpDir)test-app\android\x86 net.dot.$(TestPackageName) net.dot.MonoRunner 00:12:00 ================================================ FILE: tests/integration-tests/Android/Device.Tests.proj ================================================ false TestArch=arm64_v8a;TestPackageName=net.dot.System.Buffers.Tests;TestInstrumentationName=net.dot.MonoRunner;TestFileName=System.Buffers.Tests-arm64-v8a ================================================ FILE: tests/integration-tests/Android/Simulator.Tests.proj ================================================ TestArch=x86;TestPackageName=net.dot.System.Buffers.Tests;TestInstrumentationName=net.dot.MonoRunner;TestFileName=System.Buffers.Tests-x86 TestArch=x86_64;TestPackageName=net.dot.System.Buffers.Tests;TestInstrumentationName=net.dot.MonoRunner;TestFileName=System.Buffers.Tests-x64 ================================================ FILE: tests/integration-tests/Android/TestApks.proj ================================================ ..\..\..\Directory.Build.props ..\..\..\Directory.Build.targets $(AssetsBaseUri)/android/test-apk/$(TestArch) $(XHarnessTestDirUrl)/$(TestFileName).apk $(ArtifactsTmpDir)apk/$(TestArch) $(TestPackageName) $(TestInstrumentationName) ================================================ FILE: tests/integration-tests/Apple/Device.Commands.Tests.proj ================================================ System.Buffers.Tests $(AssetsBaseUri)/ios/test-app-new/ios-device/$(TestAppBundleName).app.zip $(ArtifactsTmpDir)test-app-new\ios-device ios-device 00:20:00 00:07:00 00:03:30 ================================================ FILE: tests/integration-tests/Apple/Device.iOS.Tests.proj ================================================ TestTarget=ios-device;TestArch=arm64;TestAppBundleName=System.Buffers.Tests.app TestTarget=ios-device;TestArch=arm64;TestAppBundleName=iOS.Simulator.PInvoke.Test.app;IncludesTestRunner=false;ExpectedExitCode=42 ================================================ FILE: tests/integration-tests/Apple/Device.tvOS.Tests.proj ================================================ TestTarget=tvos-device;TestArch=arm64;TestAppBundleName=System.Buffers.Tests.app TestTarget=tvos-device;TestArch=arm64;TestAppBundleName=iOS.Simulator.PInvoke.Test.app;IncludesTestRunner=false;ExpectedExitCode=42 ================================================ FILE: tests/integration-tests/Apple/Simulator.Commands.Tests.proj ================================================ 18.1 x64 System.Numerics.Vectors.Tests $(AssetsBaseUri)/ios/test-app-new/ios-simulator-64/$(TestArch)/$(TestAppBundleName).app.zip $(ArtifactsTmpDir)test-app-new\ios-simulator-64\$(TestArch) ios-simulator-64_$(iOSSimulatorVersionUnderTest) 00:20:00 00:07:00 00:03:30 ================================================ FILE: tests/integration-tests/Apple/Simulator.Scouting.Commands.Tests.proj ================================================ Xcode_16_beta_6 18.0 System.Numerics.Vectors.Tests $(AssetsBaseUri)/ios/test-app-new/ios-simulator-64/$(TestAppBundleName).app.zip $(ArtifactsTmpDir)test-app-new\ios-simulator-64 ios-simulator-64_$(iOSSimulatorVersionUnderTest) 00:20:00 00:07:00 00:03:30 ================================================ FILE: tests/integration-tests/Apple/Simulator.Scouting.Tests.proj ================================================ TestTarget=ios-simulator-64;TestAppBundleName=System.Numerics.Vectors.Tests.app TestTarget=ios-simulator-64;TestAppBundleName=iOS.Simulator.PInvoke.Test.app;IncludesTestRunner=false;ExpectedExitCode=42 TestTarget=maccatalyst;TestAppBundleName=System.Collections.NonGeneric.Tests.app TestTarget=maccatalyst;TestAppBundleName=iOS.Simulator.PInvoke.Test.app;IncludesTestRunner=false;ExpectedExitCode=42 TestTarget=tvos-simulator;TestAppBundleName=Microsoft.Extensions.Configuration.Ini.Tests.app ================================================ FILE: tests/integration-tests/Apple/Simulator.Tests.proj ================================================ $(HelixTargetQueue.Contains('arm64')) $(HelixTargetQueue.Contains('amd64')) arm64 x64 TestTarget=ios-simulator-64;TestArch=$(TestArch);TestAppBundleName=System.Numerics.Vectors.Tests.app TestTarget=ios-simulator-64;TestArch=$(TestArch);TestAppBundleName=iOS.Simulator.PInvoke.Test.app;IncludesTestRunner=false;ExpectedExitCode=42 TestTarget=maccatalyst;TestArch=$(TestArch);TestAppBundleName=System.Collections.NonGeneric.Tests.app TestTarget=maccatalyst;TestArch=$(TestArch);TestAppBundleName=iOS.Simulator.PInvoke.Test.app;IncludesTestRunner=false;ExpectedExitCode=42 TestTarget=tvos-simulator;TestArch=$(TestArch);TestAppBundleName=Microsoft.Extensions.Configuration.Ini.Tests.app ================================================ FILE: tests/integration-tests/Apple/SimulatorInstaller.Tests.proj ================================================ $(RepoRoot)\tests\integration-tests\Apple\helix-payloads ./simulatorinstaller-integration-tests.sh 00:05:00 ================================================ FILE: tests/integration-tests/Apple/TestAppBundle.proj ================================================ ..\..\..\Directory.Build.props ..\..\..\Directory.Build.targets $(AssetsBaseUri)/ios/test-app-new/$(TestTarget)/$(TestArch) $(AppStorageUrl)/$(TestAppBundleName).zip $(ArtifactsTmpDir)test-app-new\$(TestTarget)\$(TestArch) $(TestAppDestinationDir)\$(TestAppBundleName) $(TestTarget) 00:20:00 00:07:00 00:03:30 $(IncludesTestRunner) $(ExpectedExitCode) ================================================ FILE: tests/integration-tests/Apple/helix-payloads/simulatorinstaller-integration-tests.sh ================================================ #!/bin/bash echo "Testing simulator download availability" echo "Getting list of available simulators" IFS=$'\n' list=($(dotnet "$XHARNESS_CLI_PATH" apple simulators list | grep 'Source:')) length="${#list[@]}" echo "Found $length simulators" echo "" if [ ! "$length" -gt 0 ]; then echo "Couldn't list available simulators" 1>&2 exit 1 fi result=0 echo "Testing installed simulators and the find command" IFS=$'\n' installed_simulators=($(dotnet "$XHARNESS_CLI_PATH" apple simulators list --installed | grep 'Identifier:')) length="${#installed_simulators[@]}" if [ "$length" != "0" ]; then echo "Found $length installed simulators:" simulator_args="" for i in "${installed_simulators[@]}" do pkg_name=$(echo $i | tr -s ' ' | cut -d ' ' -f 3) echo " $pkg_name" simulator_args="$simulator_args $pkg_name" done echo "" set -x eval dotnet "$XHARNESS_CLI_PATH" apple simulators find $simulator_args if [ "$?" != 0 ]; then echo "Failed to find listed simulators" result=1 else echo "Found all listed simulators" fi else echo "No additional simulators found (probably only those coming with Xcode)" fi exit $result ================================================ FILE: tests/integration-tests/Directory.Build.props ================================================ $(NetCurrent) 10.0 .NETCoreApp test/product/ true net10.0 true $(AGENT_JOBNAME) true https://helix.dot.net 10.0.100 $(VersionPrefix)-ci false $(VersionPrefix)-dev $(ArtifactsShippingPackagesDir)/Microsoft.DotNet.XHarness.CLI.$(MicrosoftDotNetXHarnessCLIVersion).nupkg true $(BUILD_SOURCEVERSIONAUTHOR) anon msbuild ================================================ FILE: tests/integration-tests/Directory.Build.targets ================================================ true cp diagnostics.json "$HELIX_WORKITEM_UPLOAD_ROOT";$(HelixPostCommands) copy diagnostics.json "%HELIX_WORKITEM_UPLOAD_ROOT%";$(HelixPostCommands) ================================================ FILE: tests/integration-tests/README.md ================================================ # Integration tests This folder includes integration tests projects for different support platforms (iOS, Android, WASM). They are used in end-to-end testing scenarios and are referenced from `azure-pipelines-public.yml` E2E templates. In the relevant `*.proj` files one can configure various setting for execution on Helix like: - configuring the Helix queue (e.g., `osx.15.amd64.iphone.open` via `HelixTargetQueue` item group) - app bundle to download, send to Helix and test (e.g., `System.Buffers.Tests.app`) - etc. ## Testing on scouting queue NOTE: This is Apple-specific but can be applied to other platforms as well There are two test projects which can be used on scouting queues which are not used by default: - Apple/Simulator.Scouting.Tests.proj - Apple/Simulator.Scouting.Commands.Tests.proj When desired, these can be included in the `azure-pipelines-public.yml` so that the CI runs them on a desired scouting queue (check the `HelixTargetQueue` setting) with a particular version of Xcode. ================================================ FILE: tests/integration-tests/Storage.props ================================================ https://netcorenativeassets.blob.core.windows.net/resource-packages/external ================================================ FILE: tests/integration-tests/WASM/WASM.Helix.SDK.Tests.proj ================================================ https://netcorenativeassets.blob.core.windows.net/resource-packages/external/wasm/Common.Tests.zip dotnet exec "$XHARNESS_CLI_PATH" wasm test --engine=V8 --js-file=test-main.js -v --output-directory="$HELIX_WORKITEM_UPLOAD_ROOT" -- --run WasmTestRunner.dll Common.Tests.dll -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing @(TestPayloadArchive) 00:10:00 ================================================ FILE: tools/Install-XHarness.ps1 ================================================ ### This script is a quick way to install XHarness in a local folder. ### .NET SDK is installed too, everything in a local folder which can be deleted afterwards. ### ### Use the following command to run this script from anywhere: ### iex ((New-Object System.Net.WebClient).DownloadString('https://aka.ms/get-xharness-ps1')) param ( [Parameter(Mandatory = $false)] [string] $Version = "*" ) $ErrorActionPreference = "Stop" $xharness_version = "11.0.0-prerelease.$Version" # Install .NET Write-Host "Getting dotnet-install.ps1.." -ForegroundColor Cyan Invoke-WebRequest -Uri "https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1" -OutFile "dotnet-install.ps1" Write-Host "Installing .NET SDK locally to " -NoNewline -ForegroundColor Cyan Write-Host ".dotnet" -ForegroundColor Yellow ./dotnet-install.ps1 -InstallDir ./.dotnet -Channel 10.0 Write-Host ".NET installed" -ForegroundColor Cyan Write-Host "Installing XHarness in current folder" -ForegroundColor Cyan ./.dotnet/dotnet tool install --tool-path . --version $xharness_version --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json Microsoft.DotNet.XHarness.CLI Write-Host "Run following command: " -ForegroundColor Cyan -NoNewline Write-Host "`$Env:DOTNET_ROOT='$pwd\.dotnet'" -ForegroundColor Yellow Write-Host "Then run XHarness using: " -ForegroundColor Cyan -NoNewline Write-Host ".\xharness help" -ForegroundColor Yellow ================================================ FILE: tools/install-xharness.sh ================================================ #!/bin/bash ### This script is a quick way to install and test XHarness on any POSIX system. ### It installs the .NET SDK and the XHarness tool, everything in a local folder which can be deleted afterwards. ### ### Use the following command to run this script from anywhere: ### curl -L https://aka.ms/get-xharness | bash - set -e version='*' while [[ $# -gt 0 ]]; do opt="$(echo "$1" | awk '{print tolower($0)}')" case "$opt" in --version) version=$2 shift ;; *) echo "Invalid argument: $1" exit 1 ;; esac shift done xharness_version="11.0.0-prerelease.$version" here=$(pwd) dotnet_install="$here/dotnet-install.sh" echo "Getting dotnet-install.sh.." curl -L https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o "$dotnet_install" chmod u+x "$dotnet_install" dotnet_dir="$here/.dotnet" printf "Installing .NET SDK locally to \033[0;33m%s\033[0m..\n" "$dotnet_dir" $dotnet_install --install-dir "$dotnet_dir" --channel 10.0 echo 'dotnet installed' export DOTNET_ROOT="$here/.dotnet" printf "Installing XHarness.CLI locally to \033[0;33m%s\033[0m..\n" "$here" ./.dotnet/dotnet tool install --tool-path . --version "$xharness_version" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json Microsoft.DotNet.XHarness.CLI echo "XHarness.CLI installed" echo 'Run following command:' printf "\033[0;33mexport DOTNET_ROOT=\"%s\"\033[0m\n\n" "$here/.dotnet" echo 'Then run XHarness using:' printf "\033[0;33m./xharness help\033[0m\n\n" ================================================ FILE: tools/run-e2e-test.ps1 ================================================ <# .SYNOPSIS This script is a quick way to run the XHarness E2E tests. These test are located in tests/integration-tests and require the Arcade and Helix SDK. To run them, you need to invoke these through MSBuild which makes the process a bit cumbersome. This script should make things easier. .EXAMPLE .\run-e2e-test.ps1 -TestProject Apple/SimulatorInstaller.Tests.proj [-SkipBuild] #> param ( <# Path to the test project. Can be also relative to tests/integration-tests, e.g. Apple/Device.iOS.Tests.proj #> [Parameter(Mandatory = $true)] [string] $TestProject, <# Skip re-building the local package #> [switch] $SkipBuild = $false ) $repoRoot = Resolve-Path "$PSScriptRoot\.." function Write-Projects { $testRoot = "$repoRoot\tests\integration-tests\" $files = Get-ChildItem -Recurse -Include *Tests.proj -Path $testRoot | ForEach-Object { $_.FullName.Substring($testRoot.Length) } Write-Output "Possible options:" foreach ($item in $files) { " - $item" } } if (-not(Test-Path -Path $TestProject -PathType Leaf)) { $TestProject = "$repoRoot\tests\integration-tests\$TestProject" if (-not(Test-Path -Path $TestProject -PathType Leaf)) { Write-Error "The file $TestProject not found" Write-Projects Exit 1 } } if ($SkipBuild) { Write-Host -ForegroundColor Cyan "> Skipping build" } else { Write-Host -ForegroundColor Cyan "> Building Microsoft.DotNet.XHarness.CLI NuGet package" Remove-Item -Recurse -ErrorAction SilentlyContinue "$repoRoot\artifacts\tmp\Debug\Microsoft.DotNet.XHarness.CLI" Remove-Item -Recurse -ErrorAction SilentlyContinue "$repoRoot\artifacts\artifacts\packages" & "$repoRoot\Build.cmd" -pack -projects "$repoRoot\src\Microsoft.DotNet.XHarness.CLI\Microsoft.DotNet.XHarness.CLI.csproj" } $Env:BUILD_REASON = "pr" $Env:BUILD_REPOSITORY_NAME = "arcade" $Env:BUILD_SOURCEBRANCH = "test" $Env:SYSTEM_TEAMPROJECT = "dnceng" $Env:SYSTEM_ACCESSTOKEN = "" Write-Host -ForegroundColor Cyan "> Starting tests (logging to XHarness.binlog)" & "$repoRoot\Build.cmd" -configuration Debug -test -projects "$TestProject" /p:RestoreUsingNugetTargets=false /bl:.\XHarness.binlog ================================================ FILE: tools/run-e2e-test.sh ================================================ #!/bin/bash ### This script is a quick way to run the XHarness E2E tests. ### These test are located in tests/integration-tests and require the Arcade and Helix SDK. ### To run them, you need to invoke these through MSBuild which makes the process a bit cumbersome. ### This script should make things easier. ### ### Usage: ./run-e2e-test.sh Apple/SimulatorInstaller.Tests.proj [--skip-build] test_project="$1" COLOR_RED=$(tput setaf 1 2>/dev/null || true) COLOR_CYAN=$(tput setaf 6 2>/dev/null || true) COLOR_CLEAR=$(tput sgr0 2>/dev/null || true) COLOR_RESET=uniquesearchablestring FAILURE_PREFIX= if test -z "$COLOR_RED"; then FAILURE_PREFIX="** failure ** "; fi function fail () { echo "$FAILURE_PREFIX${COLOR_RED}${1//${COLOR_RESET}/${COLOR_RED}}${COLOR_CLEAR}" } function highlight () { echo "$FAILURE_PREFIX${COLOR_CYAN}${1//${COLOR_RESET}/${COLOR_CYAN}}${COLOR_CLEAR}" } function print_projects() { echo "Possible options:" prefix=$(echo "$repo_root/tests/integration-tests/" | sed "s/\//\\\\\//g") find "$repo_root/tests/integration-tests" -type f -name "*.proj" | sed "s/$prefix/ - /" } # Get current path source="${BASH_SOURCE[0]}" # resolve $source until the file is no longer a symlink while [[ -h "$source" ]]; do script_root="$( cd -P "$( dirname "$source" )" && pwd )" source="$(readlink "$source")" # if $source was a relative symlink, we need to resolve it relative to the path where the # symlink file was located [[ $source != /* ]] && source="$script_root/$source" done here="$( cd -P "$( dirname "$source" )" && pwd )" repo_root="$( cd -P "$( dirname "$here" )" && pwd )" if [ -z "$test_project" ] || [ "-h" == "$test_project" ] || [ "--help" == "$test_project" ]; then fail "Usage: ./run-e2e-test.sh Apple/SimulatorInstaller.Tests.proj [--skip-build]" print_projects exit 2 fi if [ ! -f "$test_project" ]; then test_project="$repo_root/tests/integration-tests/$test_project" fi if [ ! -f "$test_project" ]; then fail "File $1 not found" fail "File $test_project not found" print_projects exit 1 fi shift skip_build=false while (($# > 0)); do lowerI="$(echo "$1" | awk '{print tolower($0)}')" case $lowerI in --skip-build) shift skip_build=true ;; esac shift done set -e if [ "true" != "$skip_build" ]; then highlight "> Building Microsoft.DotNet.XHarness.CLI NuGet package" rm -rf "$repo_root/artifacts/tmp/Debug/Microsoft.DotNet.XHarness.CLI" "$repo_root/artifacts/packages" "$repo_root/build.sh" -build -pack --projects "$repo_root/src/Microsoft.DotNet.XHarness.CLI/Microsoft.DotNet.XHarness.CLI.csproj" else highlight "> Skipping build" fi export BUILD_REASON="dev" export BUILD_REPOSITORY_NAME="xharness" export BUILD_SOURCEBRANCH="master" export SYSTEM_TEAMPROJECT="dnceng" export SYSTEM_ACCESSTOKEN="" highlight "> Starting tests (logging to XHarness.binlog)" "$repo_root/build.sh" -configuration Debug -restore -test -projects "$test_project" /p:RestoreUsingNugetTargets=false /bl:./XHarness.binlog