Skip to content

Commit

Permalink
[nuget] add publish warning when RuntimeIdentifier is not specified (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
lachmatt committed Sep 5, 2024
1 parent 8b1bf12 commit 09a25ba
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 69 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ This component adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h
arguments to `otel-dotnet-auto-install.sh`.
- Added `OTEL_DOTNET_AUTO_LOGGER` to select preferred sink for AutoInstrumentation
diagnostic logs.
- Issue a warning before publish if auto-instrumentation NuGet
package is referenced and runtime identifier is not specified.

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,16 @@
InstrumentationTargetItems="@(InstrumentationTarget)"
RuntimeCopyLocalItems="@(RuntimeCopyLocalItems)"
UseVerboseLog="$(UseVerboseLog)" />

</Target>

<Target Name="VerifyIfRuntimeIdSpecified" BeforeTargets="Publish" Condition="'$(RuntimeIdentifier)' == '' AND $(DisableAutoInstrumentationCheckForRuntimeIdentifier) != 'true'">
<ItemGroup>
<Lines Include="RuntimeIdentifier (RID) is not set. Consider setting it to avoid copying native libraries for all of the platforms supported by the OpenTelemetry.AutoInstrumentation package." />
<Lines Include="See the docs at https://opentelemetry.io/docs/zero-code/net/nuget-packages/#using-the-nuget-packages for details." />
<Lines Include="In order to suppress this warning, set DisableAutoInstrumentationCheckForRuntimeIdentifier property to true." />
</ItemGroup>
<Warning Text="@(Lines, ' ')">
</Warning>
</Target>

</Project>
210 changes: 142 additions & 68 deletions test/NuGetPackagesTests/InstrumentationTargetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,138 @@
namespace IntegrationTests;

[Trait("Category", "EndToEnd")]
public sealed class InstrumentationTargetTests : TestHelper, IDisposable
public sealed class InstrumentationTargetTests : TestHelper
{
private const string DotNetCli = "dotnet";
private const string TargetAppName = "InstrumentationTargetTest";

private readonly string _prevWorkingDir = Directory.GetCurrentDirectory();
private readonly DirectoryInfo _tempWorkingDir;

public InstrumentationTargetTests(ITestOutputHelper output)
: base(DotNetCli, output, "nuget-packages")
{
var tempDirName = Path.Combine(
Path.GetTempPath(),
$"instr-target-test-{Guid.NewGuid():N}",
TargetAppName);
_tempWorkingDir = Directory.CreateDirectory(tempDirName);

Directory.SetCurrentDirectory(_tempWorkingDir.FullName);
}

public void Dispose()
#if !NETFRAMEWORK
[Fact]
public void PublishApplication()
{
Directory.SetCurrentDirectory(_prevWorkingDir);
_tempWorkingDir.Delete(recursive: true);
RunInTempDir(() =>
{
var tfm = $"net{Environment.Version.Major}.0";
CreateTestApp(tfm);
// Warning should be logged when runtime identifier is not specified
RunDotnetPublish(arguments: string.Empty, expectWarning: true);
// No warning should be logged when runtime identifier is specified
RunDotnetPublish(arguments: "--ucr", expectWarning: false);
// No warning should be logged when runtime identifier is not specified and check is disabled
RunDotnetPublish(arguments: "-p:DisableAutoInstrumentationCheckForRuntimeIdentifier=true", expectWarning: false);
});
}

#endif

[Fact]
public void InstrumentApplication()
{
RunInTempDir(() =>
{
string tfm;
#if NETFRAMEWORK
tfm = "net462";
#else
tfm = $"net{Environment.Version.Major}.0";
#endif
CreateTestApp(tfm);
// Read the auto-instrumentation targets file and verify detection, ignoring, and instrumentation of targets.
var projectProperties = new Dictionary<string, string> { { "TargetFramework", tfm }, };
var otelAutoTargetsFile = Path.Combine(
GetTestAssemblyPath(),
"OpenTelemetry.AutoInstrumentation.BuildTasks.targets");
var otelAutoTargets = new Project(otelAutoTargetsFile, projectProperties, null);
foreach (var instrTarget in otelAutoTargets.Items.Where(x => x.ItemType == "InstrumentationTarget"))
{
// Add instrumentation target to the app.
var instrTargetVersionRange = VersionRange.Parse(
instrTarget.Metadata.Single(x => x.Name == "TargetNuGetPackageVersionRange").EvaluatedValue);
RunDotNetCli(
$"add package {instrTarget.EvaluatedInclude} --version {instrTargetVersionRange.MinVersion}")
.Should().Be(0);
// Build should fail because the target is not yet instrumented.
var instrPackageId = instrTarget.Metadata.Single(x => x.Name == "InstrumentationNuGetPackageId")
.EvaluatedValue;
var instrPackageVersion = instrTarget.Metadata
.Single(x => x.Name == "InstrumentationNuGetPackageVersion").EvaluatedValue;
var expectedErrorMessage =
$"OpenTelemetry.AutoInstrumentation: add a reference to the instrumentation package '{instrPackageId}' version " +
$"{instrPackageVersion} or add '{instrTarget.EvaluatedInclude}' to the property 'SkippedInstrumentations' to suppress this error.";
RunDotNetCli("build", expectedErrorMessage).Should().NotBe(0);
// Explicitly disable the instrumentation target.
RunDotNetCli($"build -p:SkippedInstrumentations={instrTarget.EvaluatedInclude}").Should().Be(0);
// Add the instrumentation package, build should succeed.
RunDotNetCli($"add package {instrPackageId} --version {instrPackageVersion}").Should().Be(0);
RunDotNetCli("build").Should().Be(0);
}
});
}

private static void RunInTempDir(Action action)
{
string? prevWorkingDir = null;
DirectoryInfo? tempWorkingDir = null;
try
{
prevWorkingDir = Directory.GetCurrentDirectory();
var tempDirName = Path.Combine(
Path.GetTempPath(),
$"instr-target-test-{Guid.NewGuid():N}",
TargetAppName);

tempWorkingDir = Directory.CreateDirectory(tempDirName);
Directory.SetCurrentDirectory(tempDirName);

action();
}
finally
{
if (prevWorkingDir != null)
{
Directory.SetCurrentDirectory(prevWorkingDir);
}

tempWorkingDir?.Parent?.Delete(true);
}
}

#if !NETFRAMEWORK
private void RunDotnetPublish(string arguments, bool expectWarning)
{
const string warningMessage = "RuntimeIdentifier (RID) is not set." +
" Consider setting it to avoid copying native libraries for all of the platforms supported by the OpenTelemetry.AutoInstrumentation package." +
" See the docs at https://opentelemetry.io/docs/zero-code/net/nuget-packages/#using-the-nuget-packages for details." +
" In order to suppress this warning, set DisableAutoInstrumentationCheckForRuntimeIdentifier property to true.";

var (exitCode, standardOutput) = RunDotnetCliAndWaitForCompletion($"publish {arguments}");
exitCode.Should().Be(0);
if (expectWarning)
{
standardOutput.Should().Contain(warningMessage);
}
else
{
standardOutput.Should().NotContain(warningMessage);
}
}
#endif

private void CreateTestApp(string tfm)
{
// Disable dotnet CLI telemetry.
SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", "1");
Expand All @@ -46,13 +150,6 @@ public void InstrumentApplication()
// text replacement in the project file.
RunDotNetCli($"new console --framework net6.0").Should().Be(0);

string tfm;
#if NETFRAMEWORK
tfm = "net462";
#else
tfm = $"net{Environment.Version.Major}.0";
#endif

ChangeProjectDefaultsAndTargetFramework(tfm);

ChangeDefaultProgramToHelloWorld();
Expand All @@ -71,52 +168,23 @@ public void InstrumentApplication()
$"add package OpenTelemetry.AutoInstrumentation --prerelease").Should().Be(0);

RunDotNetCli("build").Should().Be(0);

// Read the auto-instrumentation targets file and verify detection, ignoring, and instrumentation of targets.
var projectProperties = new Dictionary<string, string>
{
{ "TargetFramework", tfm },
};
var otelAutoTargetsFile = Path.Combine(GetTestAssemblyPath(), "OpenTelemetry.AutoInstrumentation.BuildTasks.targets");
var otelAutoTargets = new Project(otelAutoTargetsFile, projectProperties, null);
foreach (var instrTarget in otelAutoTargets.Items.Where(x => x.ItemType == "InstrumentationTarget"))
{
// Add instrumentation target to the app.
var instrTargetVersionRange = VersionRange.Parse(
instrTarget.Metadata.Single(x => x.Name == "TargetNuGetPackageVersionRange").EvaluatedValue);
RunDotNetCli($"add package {instrTarget.EvaluatedInclude} --version {instrTargetVersionRange.MinVersion}").Should().Be(0);

// Build should fail because the target is not yet instrumented.
var instrPackageId = instrTarget.Metadata.Single(x => x.Name == "InstrumentationNuGetPackageId").EvaluatedValue;
var instrPackageVersion = instrTarget.Metadata.Single(x => x.Name == "InstrumentationNuGetPackageVersion").EvaluatedValue;
var expectedErrorMessage =
$"OpenTelemetry.AutoInstrumentation: add a reference to the instrumentation package '{instrPackageId}' version " +
$"{instrPackageVersion} or add '{instrTarget.EvaluatedInclude}' to the property 'SkippedInstrumentations' to suppress this error.";

RunDotNetCli("build", expectedErrorMessage).Should().NotBe(0);

// Explicitly disable the instrumentation target.
RunDotNetCli($"build -p:SkippedInstrumentations={instrTarget.EvaluatedInclude}").Should().Be(0);

// Add the instrumentation package, build should succeed.
RunDotNetCli($"add package {instrPackageId} --version {instrPackageVersion}").Should().Be(0);
RunDotNetCli("build").Should().Be(0);
}
}

private void ChangeDefaultProgramToHelloWorld()
{
const string ProgramContent = @"
public static class Program
{
public static void Main()
{
System.Console.WriteLine(""Hello World!"");
}
}
";
const string programContent = """

public static class Program
{
public static void Main()
{
System.Console.WriteLine("Hello World!");
}
}

""";

File.WriteAllText("Program.cs", ProgramContent);
File.WriteAllText("Program.cs", programContent);
}

private void ChangeProjectDefaultsAndTargetFramework(string tfm)
Expand All @@ -130,6 +198,17 @@ private void ChangeProjectDefaultsAndTargetFramework(string tfm)
}

private int RunDotNetCli(string arguments, string? expectedOutputFragment = null)
{
var (exitCode, standardOutput) = RunDotnetCliAndWaitForCompletion(arguments);
if (expectedOutputFragment is not null)
{
standardOutput.Should().Contain(expectedOutputFragment);
}

return exitCode;
}

private (int ExitCode, string StandardOutput) RunDotnetCliAndWaitForCompletion(string arguments)
{
Output.WriteLine($"Running: {DotNetCli} {arguments}");

Expand All @@ -148,12 +227,7 @@ private int RunDotNetCli(string arguments, string? expectedOutputFragment = null
Output.WriteLine("Exit Code: " + process.ExitCode);
Output.WriteResult(helper);

if (expectedOutputFragment is not null)
{
helper.StandardOutput.Should().Contain(expectedOutputFragment);
}

processTimeout.Should().BeFalse("Test application timed out");
return process.ExitCode;
return (process.ExitCode, helper.StandardOutput);
}
}

0 comments on commit 09a25ba

Please sign in to comment.