| File: RustLanguageSupportTests.cs | Web Access |
| Project: src\tests\Aspire.Hosting.CodeGeneration.Rust.Tests\Aspire.Hosting.CodeGeneration.Rust.Tests.csproj (Aspire.Hosting.CodeGeneration.Rust.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.TypeSystem; namespace Aspire.Hosting.CodeGeneration.Rust.Tests; public class RustLanguageSupportTests(ITestOutputHelper outputHelper) { private readonly RustLanguageSupport _languageSupport = new(); [Fact] public void Scaffold_CreatesRustAppHostFilesOnly() { using var workspace = TemporaryWorkspace.Create(outputHelper); var files = _languageSupport.Scaffold(new ScaffoldRequest { TargetPath = workspace.Path, ProjectName = "RustApp" }); Assert.Collection( files.Keys.Order(StringComparer.Ordinal), key => Assert.Equal("Cargo.toml", key), key => Assert.Equal("apphost.rs", key), key => Assert.Equal("apphost.run.json", key)); Assert.Contains("#[path = \".aspire/modules/mod.rs\"]", files["apphost.rs"], StringComparison.Ordinal); Assert.Contains("let builder = create_builder(None)?;", files["apphost.rs"], StringComparison.Ordinal); Assert.Contains("app.run(None)?;", files["apphost.rs"], StringComparison.Ordinal); Assert.Contains("[[bin]]", files["Cargo.toml"], StringComparison.Ordinal); Assert.Contains("name = \"apphost\"", files["Cargo.toml"], StringComparison.Ordinal); Assert.Contains("path = \"apphost.rs\"", files["Cargo.toml"], StringComparison.Ordinal); } [Fact] public void Detect_ReturnsRustAppHostWhenMarkerAndCargoExist() { using var workspace = TemporaryWorkspace.Create(outputHelper); File.WriteAllText(Path.Combine(workspace.Path, "apphost.rs"), "// marker"); File.WriteAllText(Path.Combine(workspace.Path, "Cargo.toml"), "[package]"); var result = _languageSupport.Detect(workspace.Path); Assert.True(result.IsValid); Assert.Equal("rust", result.Language); Assert.Equal("apphost.rs", result.AppHostFile); } [Fact] public void Detect_DoesNotTreatTypeScriptAppHostAsRust() { using var workspace = TemporaryWorkspace.Create(outputHelper); File.WriteAllText(Path.Combine(workspace.Path, "apphost.ts"), "// typescript"); File.WriteAllText(Path.Combine(workspace.Path, "Cargo.toml"), "[package]"); var result = _languageSupport.Detect(workspace.Path); Assert.False(result.IsValid); } [Fact] public void Detect_RequiresCargoManifest() { using var workspace = TemporaryWorkspace.Create(outputHelper); File.WriteAllText(Path.Combine(workspace.Path, "apphost.rs"), "// marker"); var result = _languageSupport.Detect(workspace.Path); Assert.False(result.IsValid); } [Fact] public void GetRuntimeSpec_UsesCargoRun() { var runtimeSpec = _languageSupport.GetRuntimeSpec(); Assert.Equal("rust", runtimeSpec.Language); Assert.Equal("Rust", runtimeSpec.DisplayName); Assert.Equal("Rust", runtimeSpec.CodeGenLanguage); Assert.Equal(["apphost.rs"], runtimeSpec.DetectionPatterns); Assert.Equal("rust", runtimeSpec.ExtensionLaunchCapability); Assert.Equal("cargo", runtimeSpec.Execute.Command); Assert.Equal(["run", "--bin", "apphost", "--"], runtimeSpec.Execute.Args); } [Fact] public void GetRuntimeSpec_NamesTheScaffoldedBinarySoASecondBinTargetStaysUnambiguous() { using var workspace = TemporaryWorkspace.Create(outputHelper); var files = _languageSupport.Scaffold(new ScaffoldRequest { TargetPath = workspace.Path, ProjectName = "RustApp" }); // Once the package declares a second [[bin]], a bare `cargo run` fails with "could not determine // which binary to run" and the app host never starts, so the launch command has to name the binary // the scaffolded manifest declares. var multiBinaryManifest = files["Cargo.toml"] + """ [[bin]] name = "migrate" path = "migrate.rs" """; var runtimeSpec = _languageSupport.GetRuntimeSpec(); Assert.Equal(["apphost", "migrate"], ReadBinTargetNames(multiBinaryManifest)); Assert.Equal(["run", "--bin", "apphost", "--"], runtimeSpec.Execute.Args); } // Bin targets are declared in Cargo.toml as: // [[bin]] // name = "apphost" // path = "apphost.rs" // Only the scaffolded shape is handled: a table header on its own line followed by unquoted keys. private static string[] ReadBinTargetNames(string manifest) { const string namePrefix = "name = \""; var names = new List<string>(); var inBinTable = false; foreach (var line in manifest.Split('\n')) { var trimmed = line.Trim(); if (trimmed.StartsWith('[')) { inBinTable = trimmed == "[[bin]]"; } else if (inBinTable && trimmed.StartsWith(namePrefix, StringComparison.Ordinal)) { names.Add(trimmed[namePrefix.Length..].TrimEnd('"')); } } return [.. names]; } }