Skip to content

Commit

Permalink
SLVS-1673 Include environment variables in generated compilation db (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
georgii-borovinskikh-sonarsource authored Dec 16, 2024
1 parent 81b7d59 commit e03402c
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,22 @@

using Newtonsoft.Json;

namespace SonarLint.VisualStudio.CFamily.CMake
namespace SonarLint.VisualStudio.CFamily.CompilationDatabase;

/// <summary>
/// Schema based on https://clang.llvm.org/docs/JSONCompilationDatabase.html
/// </summary>
public class CompilationDatabaseEntry
{
/// <summary>
/// Schema based on https://clang.llvm.org/docs/JSONCompilationDatabase.html
/// </summary>
public class CompilationDatabaseEntry
{
[JsonProperty("directory")]
public string Directory { get; set; }
[JsonProperty("directory")]
public string Directory { get; set; }

[JsonProperty("command")]
public string Command { get; set; }
[JsonProperty("command")]
public string Command { get; set; }

[JsonProperty("file")]
public string File { get; set; }
[JsonProperty("file")]
public string File { get; set; }

[JsonProperty("arguments")]
public string Arguments { get; set; }
}
[JsonProperty("environment")]
public IEnumerable<string> Environment { get; set; }
}
20 changes: 20 additions & 0 deletions src/Core.UnitTests/EnvironmentVariableProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ namespace SonarLint.VisualStudio.Core.UnitTests
[TestClass]
public class EnvironmentVariableProviderTests
{
[TestMethod]
public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported<EnvironmentVariableProvider, IEnvironmentVariableProvider>();

[TestMethod]
public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent<EnvironmentVariableProvider>();

[TestMethod]
public void GetAll_ReturnsExpectedValues()
{
var testSubject = EnvironmentVariableProvider.Instance;
using var environmentVariableScope = new EnvironmentVariableScope();
environmentVariableScope.SetVariable("VAR1", "VAL1");
environmentVariableScope.SetVariable("VAR2", "VAL2");

var variables = testSubject.GetAll();

variables.Should().HaveCountGreaterThan(2);
variables.Should().Contain([("VAR1", "VAL1"), ("VAR2", "VAL2")]);
}

[TestMethod]
[DataRow(null)]
[DataRow("")]
Expand Down
26 changes: 25 additions & 1 deletion src/Core/EnvironmentVariableProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
*/

using System;
using System.Collections;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
using SonarLint.VisualStudio.Core;

namespace SonarLint.VisualStudio.Core
{
Expand All @@ -38,12 +41,20 @@ public interface IEnvironmentVariableProvider
/// Gets the path to the system special folder that is identified by the specified enumeration.
/// </summary>
string GetFolderPath(Environment.SpecialFolder folder);

/// <summary>
/// Gets the current process environment variables
/// </summary>
List<(string name, string value)> GetAll();
}

[Export(typeof(IEnvironmentVariableProvider))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class EnvironmentVariableProvider : IEnvironmentVariableProvider
{
public static EnvironmentVariableProvider Instance { get; } = new EnvironmentVariableProvider();
public static EnvironmentVariableProvider Instance { get; } = new();

[ImportingConstructor]
private EnvironmentVariableProvider()
{
// no-op
Expand All @@ -60,6 +71,19 @@ public string TryGet(string variableName)
}

public string GetFolderPath(Environment.SpecialFolder folder) => Environment.GetFolderPath(folder);

public List<(string name, string value)> GetAll()
{
var variables = new List<(string name, string value)>();
foreach (DictionaryEntry environmentVariable in Environment.GetEnvironmentVariables())
{
if (environmentVariable is { Key: string variableName, Value: string variableValue })
{
variables.Add((variableName, variableValue));
}
}
return variables;
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

using NSubstitute.ReturnsExtensions;
using SonarLint.VisualStudio.CFamily;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.CFamily;
using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject;

Expand All @@ -28,60 +29,155 @@ namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject;
[TestClass]
public class VCXCompilationDatabaseProviderTests
{
private const string CDFile = "cdfilevalue";
private const string CDDirectory = "cddirectoryvalue";
private const string CDCommand = "cdcommandvalue";
private const string EnvInclude = "envincludevalue";
private const string SourceFilePath = "some path";
private IVCXCompilationDatabaseStorage storage;
private IFileConfigProvider fileConfigProvider;
private VCXCompilationDatabaseProvider testSubject;
private IEnvironmentVariableProvider envVarProvider;

[TestInitialize]
public void TestInitialize()
{
storage = Substitute.For<IVCXCompilationDatabaseStorage>();
fileConfigProvider = Substitute.For<IFileConfigProvider>();
envVarProvider = Substitute.For<IEnvironmentVariableProvider>();
envVarProvider.GetAll().Returns([]);
}

[TestMethod]
public void MefCtor_CheckIsExported() =>
public void MefCtor_CheckIsExported()
{
envVarProvider.GetAll().Returns([]);
MefTestHelpers.CheckTypeCanBeImported<VCXCompilationDatabaseProvider, IVCXCompilationDatabaseProvider>(
MefTestHelpers.CreateExport<IVCXCompilationDatabaseStorage>(),
MefTestHelpers.CreateExport<IEnvironmentVariableProvider>(envVarProvider),
MefTestHelpers.CreateExport<IFileConfigProvider>());
}

[TestMethod]
public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent<VCXCompilationDatabaseProvider>();

[TestInitialize]
public void TestInitialize()
{
storage = Substitute.For<IVCXCompilationDatabaseStorage>();
fileConfigProvider = Substitute.For<IFileConfigProvider>();
testSubject = new VCXCompilationDatabaseProvider(
storage,
fileConfigProvider);
}

[TestMethod]
public void CreateOrNull_NoFileConfig_ReturnsNull()
{
fileConfigProvider.Get(SourceFilePath, default).ReturnsNull();
var testSubject = new VCXCompilationDatabaseProvider(
storage,
envVarProvider,
fileConfigProvider);

testSubject.CreateOrNull(SourceFilePath).Should().BeNull();

storage.DidNotReceiveWithAnyArgs().CreateDatabase(default);
storage.DidNotReceiveWithAnyArgs().CreateDatabase(default, default, default, default);
}

[TestMethod]
public void CreateOrNull_FileConfig_CantStore_ReturnsNull()
{
var fileConfig = Substitute.For<IFileConfig>();
var fileConfig = GetFileConfig();
fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig);
storage.CreateDatabase(fileConfig).ReturnsNull();
storage.CreateDatabase(default, default, default, default).ReturnsNullForAnyArgs();
var testSubject = new VCXCompilationDatabaseProvider(
storage,
envVarProvider,
fileConfigProvider);

testSubject.CreateOrNull(SourceFilePath).Should().BeNull();

storage.Received().CreateDatabase(fileConfig);
storage.Received().CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Any<IEnumerable<string>>());
}

[TestMethod]
public void CreateOrNull_FileConfig_StoresAndReturnsHandle()
{
var fileConfig = Substitute.For<IFileConfig>();
var fileConfig = GetFileConfig();
fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig);
var compilationDatabaseHandle = Substitute.For<ICompilationDatabaseHandle>();
storage.CreateDatabase(fileConfig).Returns(compilationDatabaseHandle);
storage.CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Any<IEnumerable<string>>()).Returns(compilationDatabaseHandle);
var testSubject = new VCXCompilationDatabaseProvider(
storage,
envVarProvider,
fileConfigProvider);

testSubject.CreateOrNull(SourceFilePath).Should().Be(compilationDatabaseHandle);
}

[TestMethod]
public void CreateOrNull_NoEnvIncludeInFileConfig_UsesStatic()
{
var fileConfig = GetFileConfig(null);
fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig);
envVarProvider.GetAll().Returns([("Var1", "Value1"), ("INCLUDE", "static"), ("Var2", "Value2")]);
var testSubject = new VCXCompilationDatabaseProvider(
storage,
envVarProvider,
fileConfigProvider);

testSubject.CreateOrNull(SourceFilePath);

storage.Received(1).CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Is<IEnumerable<string>>(x => x.SequenceEqual(new [] { "Var1=Value1", "INCLUDE=static", "Var2=Value2" })));
}

[TestMethod]
public void CreateOrNull_FileConfigHasEnvInclude_UsesDynamic()
{
var fileConfig = GetFileConfig(EnvInclude);
fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig);
envVarProvider.GetAll().Returns([("Var1", "Value1"), ("INCLUDE", "static"), ("Var2", "Value2")]);
var testSubject = new VCXCompilationDatabaseProvider(
storage,
envVarProvider,
fileConfigProvider);

testSubject.CreateOrNull(SourceFilePath);

storage.Received(1).CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Is<IEnumerable<string>>(x => x.SequenceEqual(new [] { "Var1=Value1", "Var2=Value2", $"INCLUDE={EnvInclude}"})));
}

[TestMethod]
public void CreateOrNull_NoStaticInclude_UsesDynamic()
{
var fileConfig = GetFileConfig(EnvInclude);
fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig);
envVarProvider.GetAll().Returns([("Var1", "Value1"), ("Var2", "Value2")]);
var testSubject = new VCXCompilationDatabaseProvider(
storage,
envVarProvider,
fileConfigProvider);

testSubject.CreateOrNull(SourceFilePath);

storage.Received(1).CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Is<IEnumerable<string>>(x => x.SequenceEqual(new [] { "Var1=Value1", "Var2=Value2", $"INCLUDE={EnvInclude}"})));
}

[TestMethod]
public void CreateOrNull_StaticEnvVarsAreCached()
{
var fileConfig = GetFileConfig();
fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig);
envVarProvider.GetAll().Returns([("Var1", "Value1"), ("Var2", "Value2")]);
var testSubject = new VCXCompilationDatabaseProvider(
storage,
envVarProvider,
fileConfigProvider);

testSubject.CreateOrNull(SourceFilePath);
testSubject.CreateOrNull(SourceFilePath);
testSubject.CreateOrNull(SourceFilePath);

envVarProvider.Received(1).GetAll();
}

private IFileConfig GetFileConfig(string envInclude = EnvInclude)
{
var fileConfig = Substitute.For<IFileConfig>();
fileConfig.CDFile.Returns(CDFile);
fileConfig.CDDirectory.Returns(CDDirectory);
fileConfig.CDCommand.Returns(CDCommand);
fileConfig.EnvInclude.Returns(envInclude);
return fileConfig;
}
}
Loading

0 comments on commit e03402c

Please sign in to comment.