diff --git a/.gitignore b/.gitignore
index c166b1576..30fc9f202 100644
--- a/.gitignore
+++ b/.gitignore
@@ -360,3 +360,5 @@ MigrationBackup/
# some ide stuff
.idea
+
+.vscode/
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 3873efc7d..3fb05e9d6 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -15,7 +15,7 @@
-
+
diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/InstallCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/InstallCommand.cs
index f28dea399..8ad7580ef 100644
--- a/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/InstallCommand.cs
+++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/InstallCommand.cs
@@ -121,6 +121,30 @@ protected override async Task InvokeInternal(ILogger logger)
private async Task Install(Simulator simulator)
{
+ if (simulator.Source is null)
+ {
+ var xcodeVersion = await GetXcodeVersion();
+ if (!simulator.IsCryptexDiskImage || Version.Parse(xcodeVersion).Major < 16)
+ {
+ throw new Exception($"Cannot download simulator: {simulator.Name} from source nor through Xcode: {xcodeVersion}");
+ }
+ else
+ {
+ 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, "-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;
+ }
+ }
+ }
+
var filename = Path.GetFileName(simulator.Source);
var downloadPath = Path.Combine(TempDirectory, filename);
var download = true;
diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/Simulator.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/Simulator.cs
index 87be8a453..f16fcfc9e 100644
--- a/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/Simulator.cs
+++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/Simulator.cs
@@ -9,21 +9,25 @@ 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? Source { get; }
public string InstallPrefix { get; }
public long FileSize { get; }
public bool IsDmgFormat { get; }
+ public bool IsCryptexDiskImage { get; }
- public Simulator(string name, string identifier, string version, string source, string installPrefix, long fileSize)
+ public Simulator(string name, string platform, string identifier, string version, string? source, string installPrefix, long fileSize, bool isCryptexDiskImage)
{
Name = name;
+ Platform = platform;
Identifier = identifier;
Version = version;
Source = source;
InstallPrefix = installPrefix;
FileSize = fileSize;
IsDmgFormat = Identifier.StartsWith("com.apple.dmg.", StringComparison.OrdinalIgnoreCase);
+ IsCryptexDiskImage = isCryptexDiskImage;
}
}
diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/SimulatorsCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/SimulatorsCommand.cs
index ea3429563..cbfb51f32 100644
--- a/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/SimulatorsCommand.cs
+++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/Simulators/SimulatorsCommand.cs
@@ -108,13 +108,6 @@ protected async Task> GetAvailableSimulators()
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");
- if (sourceNode is null)
- {
- // It seems that Apple can list beta simulators in the index file, but they do not provide a source for downloading them (eg: iOS 18.0 beta Simulator Runtime).
- // In such cases log a warning and skip trying to download such simulator as they are not publicly available.
- Logger.LogWarning($"Simulator with name: '{nameNode.InnerText}' version: '{versionNode.InnerText}' identifier: '{identifierNode.InnerText}' has no source for download, skipping...");
- continue;
- }
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");
@@ -144,13 +137,51 @@ protected async Task> GetAvailableSimulators()
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);
+ var isCryptexDiskImage = false;
+ if (source is null)
+ {
+ // We allow source to be missing for newer simulators (e.g., iOS 18+ available from Xcode 16) that use cryptographically-sealed archives.
+ // Eg.:
+ //
+ // category
+ // simulator
+ // contentType
+ // cryptexDiskImage
+ // ...
+ // These images are downloaded and installed through xcodebuild instead.
+ // https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes#Install-and-manage-Simulator-runtimes-from-the-command-line
+ var contentTypeNode = downloadable.SelectSingleNode("key[text()='contentType']/following-sibling::string") ?? throw new Exception("ContentType node not found");
+ var contentType = contentTypeNode.InnerText;
+ if (contentType.Equals("cryptexDiskImage", StringComparison.OrdinalIgnoreCase))
+ {
+ isCryptexDiskImage = true;
+ Logger.LogInformation($"Simulator with name: '{nameNode.InnerText}' version: '{versionNode.InnerText}' identifier: '{identifierNode.InnerText}' has no source but it is a cryptex disk image which can be downloaded through xcodebuild.");
+ }
+ else
+ {
+ Logger.LogWarning($"Simulator with name: '{nameNode.InnerText}' version: '{versionNode.InnerText}' identifier: '{identifierNode.InnerText}' has no source for download nor it is a cryptex disk image, skipping...");
+ continue;
+ }
+ }
+
simulators.Add(new Simulator(
name: name,
+ platform: platform,
identifier: ReplaceStringUsingKey(identifierNode.InnerText, dict),
version: versionNode.InnerText,
- source: ReplaceStringUsingKey(sourceNode.InnerText, dict),
+ source: source,
installPrefix: installPrefix,
- fileSize: (long)parsedFileSize));
+ fileSize: (long)parsedFileSize,
+ isCryptexDiskImage: isCryptexDiskImage
+ ));
}
return simulators;
@@ -182,6 +213,8 @@ protected async Task> GetAvailableSimulators()
{
return null;
}
+ Logger.LogDebug($"Listing runtime disk images via returned: {json}");
+
string simulatorRuntime = "";
string simulatorVersion = "";
@@ -385,7 +418,7 @@ private async Task DownloadFile(string url, string destinationPath)
return false;
}
- private async Task GetXcodeVersion()
+ protected async Task GetXcodeVersion()
{
if (_xcodeVersion is not null)
{
diff --git a/tests/integration-tests/Apple/Simulator.Commands.Tests.proj b/tests/integration-tests/Apple/Simulator.Commands.Tests.proj
index 147ccede8..8fa960587 100644
--- a/tests/integration-tests/Apple/Simulator.Commands.Tests.proj
+++ b/tests/integration-tests/Apple/Simulator.Commands.Tests.proj
@@ -4,6 +4,10 @@
+
+ 16.4
+
+
System.Numerics.Vectors.Tests
$(AssetsBaseUri)/ios/test-app/ios-simulator-64/$(TestAppBundleName).app.zip
@@ -21,13 +25,15 @@
- ios-simulator-64
+ ios-simulator-64_$(iOSSimulatorVersionUnderTest)
00:20:00
00:07:00
00:03:30
+
+
+
+
+
+
+
+
+ Xcode_16_beta_6
+ 18.0
+
+
+
+ System.Numerics.Vectors.Tests
+ $(AssetsBaseUri)/ios/test-app/ios-simulator-64/$(TestAppBundleName).app.zip
+ $(ArtifactsTmpDir)test-app\ios-simulator-64
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ios-simulator-64_$(iOSSimulatorVersionUnderTest)
+ 00:20:00
+ 00:07:00
+ 00:03:30
+
+
+
+
+
+
+
+
diff --git a/tests/integration-tests/README.md b/tests/integration-tests/README.md
new file mode 100644
index 000000000..7769c853a
--- /dev/null
+++ b/tests/integration-tests/README.md
@@ -0,0 +1,20 @@
+# 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.13.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.