Skip to content

Commit

Permalink
Switch LightupAppActivation tests to component tests targeting addi…
Browse files Browse the repository at this point in the history
…tional deps (#78139)

The `LightupAppActivation` tests are testing handling of additional deps. The actual app and lib are not really interesting, so we can switch them to the dependency resolution tests that use a mock coreclr and don't actually run an app. On my Windows machine, this goes from ~20s to run the deleted tests to ~700ms to run the new tests.
  • Loading branch information
elinor-fung authored Nov 10, 2022
1 parent f101c9c commit 6a67628
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 846 deletions.

This file was deleted.

39 changes: 0 additions & 39 deletions src/installer/tests/Assets/TestProjects/LightupClient/Program.cs

This file was deleted.

This file was deleted.

21 changes: 0 additions & 21 deletions src/installer/tests/Assets/TestProjects/LightupLib/Program.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// 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.DotNet.Cli.Build;
using Microsoft.DotNet.Cli.Build.Framework;
using Xunit;

namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
{
public class AdditionalDeps : DependencyResolutionBase, IClassFixture<AdditionalDeps.SharedTestState>
{
private SharedTestState SharedState { get; }

// Shared state has a NetCoreApp with the two versions below
public const string NetCoreAppVersion = "4.1.1";
public const string NetCoreAppVersionPreview = "4.1.2-preview.2";

public AdditionalDeps(SharedTestState sharedState)
{
SharedState = sharedState;
}

// Additional deps can point to a directory. The host looks for deps.json files in:
// <additional_deps_dir>/shared/<fx_name>/<version>
// It uses the version closest to the framework version with a matching major/minor
// and equal or lesser patch, release or pre-release.
[Theory]
// exact match
[InlineData("4.1.1", new string[] { "4.1.0", "4.1.1" }, "4.1.1")]
[InlineData("4.1.2-preview.2", new string[] { "4.1.1", "4.1.2-preview.2" }, "4.1.2-preview.2")]
// lower patch version
[InlineData("4.1.1", new string[] { "4.1.0", "4.1.2-preview.1" }, "4.1.0")]
[InlineData("4.1.2-preview.2", new string[] { "4.1.1", "4.1.2" }, "4.1.1")]
// lower prerelease
[InlineData("4.1.1", new string[] { "4.1.0", "4.1.1-preview.1" }, "4.1.1-preview.1")]
[InlineData("4.1.2-preview.2", new string[] { "4.1.1", "4.1.2-preview.1" }, "4.1.2-preview.1")]
// no match
[InlineData("4.1.1", new string[] { "4.0.0", "4.1.2", "4.2.0" }, null)]
[InlineData("4.1.2-preview.2", new string[] { "4.0.0", "4.1.2", "4.2.0" }, null)]
public void DepsDirectory(string fxVersion, string[] versions, string usedVersion)
{
string additionalDepsDirectory = SharedFramework.CalculateUniqueTestDirectory(Path.Combine(SharedState.Location, "additionalDeps"));
using (TestArtifact artifact = new TestArtifact(additionalDepsDirectory))
{
string depsJsonName = Path.GetFileName(SharedState.AdditionalDepsComponent.DepsJson);
foreach (string version in versions)
{
string path = Path.Combine(additionalDepsDirectory, "shared", MicrosoftNETCoreApp, version);
Directory.CreateDirectory(path);
File.Copy(
SharedState.AdditionalDepsComponent.DepsJson,
Path.Combine(path, depsJsonName),
true);
}

TestApp app = SharedState.FrameworkReferenceApp;
if (fxVersion != NetCoreAppVersion)
{
// Make a copy of the app and update its framework version
app = SharedState.FrameworkReferenceApp.Copy();
RuntimeConfig.FromFile(app.RuntimeConfigJson)
.RemoveFramework(MicrosoftNETCoreApp)
.WithFramework(MicrosoftNETCoreApp, fxVersion)
.Save();
}

CommandResult result = SharedState.DotNetWithNetCoreApp.Exec(Constants.AdditionalDeps.CommandLineArgument, additionalDepsDirectory, app.AppDll)
.EnableTracingAndCaptureOutputs()
.Execute();

result.Should().Pass();
if (string.IsNullOrEmpty(usedVersion))
{
result.Should().HaveStdErrContaining($"No additional deps directory less than or equal to [{fxVersion}] found with same major and minor version.");
}
else
{
result.Should().HaveUsedAdditionalDeps(Path.Combine(additionalDepsDirectory, "shared", MicrosoftNETCoreApp, usedVersion, depsJsonName));
}
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void DepsFile(bool dependencyExists)
{
string additionalLibName = SharedState.AdditionalDepsComponent.AssemblyName;
string additionalDepsFile = SharedState.AdditionalDepsComponent.DepsJson;

TestApp app = SharedState.FrameworkReferenceApp;
if (!dependencyExists)
{
// Make a copy of the app and delete the app-local dependency
app = SharedState.FrameworkReferenceApp.Copy();
File.Delete(Path.Combine(app.Location, $"{additionalLibName}.dll"));
}

CommandResult result = SharedState.DotNetWithNetCoreApp.Exec(Constants.AdditionalDeps.CommandLineArgument, additionalDepsFile, app.AppDll)
.EnableTracingAndCaptureOutputs()
.Execute(expectedToFail: !dependencyExists);

result.Should().HaveUsedAdditionalDeps(additionalDepsFile);
if (dependencyExists)
{
result.Should().Pass()
.And.HaveResolvedAssembly(Path.Combine(app.Location, $"{additionalLibName}.dll"));
}
else
{
result.Should().Fail()
.And.HaveStdErrContaining(
$"Error:{Environment.NewLine}" +
$" An assembly specified in the application dependencies manifest ({additionalLibName}.deps.json) was not found:" + Environment.NewLine +
$" package: \'{additionalLibName}\', version: \'1.0.0\'" + Environment.NewLine +
$" path: \'{additionalLibName}.dll\'");
}
}

[Fact]
public void InvalidJson()
{
string invalidDepsFile = Path.Combine(SharedState.Location, "invalid.deps.json");
try
{
File.WriteAllText(invalidDepsFile, "{");

SharedState.DotNetWithNetCoreApp.Exec(Constants.AdditionalDeps.CommandLineArgument, invalidDepsFile, SharedState.FrameworkReferenceApp.AppDll)
.EnableTracingAndCaptureOutputs()
.Execute(expectedToFail: true)
.Should().Fail()
.And.HaveUsedAdditionalDeps(invalidDepsFile)
.And.HaveStdErrContaining($"Error initializing the dependency resolver: An error occurred while parsing: {invalidDepsFile}");
}
finally
{
FileUtils.DeleteFileIfPossible(invalidDepsFile);
}
}

public class SharedTestState : DependencyResolutionBase.SharedTestStateBase
{
public DotNetCli DotNetWithNetCoreApp { get; }

public TestApp FrameworkReferenceApp { get; }

public TestApp AdditionalDepsComponent { get; }

public SharedTestState()
{
DotNetWithNetCoreApp = DotNet("WithNetCoreApp")
.AddMicrosoftNETCoreAppFrameworkMockCoreClr(NetCoreAppVersion)
.AddMicrosoftNETCoreAppFrameworkMockCoreClr(NetCoreAppVersionPreview)
.Build();

AdditionalDepsComponent = CreateComponentWithNoDependencies();

TestApp app = CreateFrameworkReferenceApp(MicrosoftNETCoreApp, NetCoreAppVersion);
FrameworkReferenceApp = NetCoreAppBuilder.PortableForNETCoreApp(app)
.WithProject(p => p.WithAssemblyGroup(null, g => g.WithMainAssembly()))
.Build(app);

// Copy dependency next to app
File.Copy(AdditionalDepsComponent.AppDll, Path.Combine(FrameworkReferenceApp.Location, $"{AdditionalDepsComponent.AssemblyName}.dll"));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ public static AndConstraint<CommandResultAssertions> NotHaveResolvedComponentDep
return assertion.NotHaveResolvedComponentDependencyContaining(native_search_paths, RelativePathsToAbsoluteAppPaths(path, app));
}

public static AndConstraint<CommandResultAssertions> HaveUsedAdditionalDeps(this CommandResultAssertions assertion, string depsFilePath)
{
return assertion.HaveStdErrContaining($"Using specified additional deps.json: '{depsFilePath}'");
}

private static string GetAppMockPropertyValue(CommandResultAssertions assertion, string propertyName) =>
GetMockPropertyValue(assertion, $"mock property[{propertyName}] = ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,46 @@ protected override void RunTest(string testAssemblyName, string appAsmVersion, s
}
}

public class AdditionalDepsPerAssemblyVersionResolution :
PerAssemblyVersionResolutionBase,
IClassFixture<PerAssemblyVersionResolutionBase.SharedTestState>
{
public AdditionalDepsPerAssemblyVersionResolution(SharedTestState sharedState)
: base(sharedState)
{
}

protected override void RunTest(string testAssemblyName, string appAsmVersion, string appFileVersion, bool appWins)
{
using (TestApp additionalDependency = TestApp.CreateEmpty("additionalDeps"))
{
// Additional deps are treated as part of app dependencies.
// The result for whether the app wins should be the same whether the dependency is
// specified via additional deps or by the app itself.
NetCoreAppBuilder builder = NetCoreAppBuilder.PortableForNETCoreApp(additionalDependency)
.WithPackage(TestVersionsPackage, "1.0.0", lib => lib
.WithAssemblyGroup(null, g => g
.WithAsset(testAssemblyName + ".dll", rf => rf
.WithVersion(appAsmVersion, appFileVersion))));
builder.Build(additionalDependency);

TestApp app = SharedState.FrameworkReferenceApp.Copy();
string appTestAssemblyPath = Path.Combine(app.Location, $"{testAssemblyName}.dll");
File.WriteAllText(Path.Combine(app.Location, $"{testAssemblyName}.dll"), null);

string expectedTestAssemblyPath = appWins
? appTestAssemblyPath
: Path.Combine(SharedState.DotNetWithNetCoreApp.GreatestVersionSharedFxPath, $"{testAssemblyName}.dll");
SharedState.DotNetWithNetCoreApp.Exec(Constants.AdditionalDeps.CommandLineArgument, additionalDependency.DepsJson, app.AppDll)
.EnableTracingAndCaptureOutputs()
.Execute()
.Should().Pass()
.And.HaveUsedAdditionalDeps(additionalDependency.DepsJson)
.And.HaveResolvedAssembly(expectedTestAssemblyPath);
}
}
}

public class ComponentPerAssemblyVersionResolution :
PerAssemblyVersionResolutionBase,
IClassFixture<PerAssemblyVersionResolutionBase.SharedTestState>
Expand Down
Loading

0 comments on commit 6a67628

Please sign in to comment.