0

I'm setting up a very basic build pipeline in Azure Pipelines that simply builds the solution and runs all unit tests every time a commit is made to the target branch. My application code is using preprocessor directives to check for an environment variable that I'm setting as part of the build pipeline, but so far I don't seem to be doing that correctly. The section of code that should be excluded in the pipeline is always being run.

I created a very simple unit test to troubleshoot this:

[Fact]
public void PipelineTest()
{
#if AZURE_PIPELINE

    // Return immediately when in the pipeline
    return;

#endif

    // Intentionally fail when not in the pipeline
    true.Should().BeFalse();
}

My pipeline's YML file is about as simple as it gets:

trigger:
- develop

pool:
  vmImage: 'windows-latest'

variables:
  solution: '**/*.sln'
  buildPlatform: 'Any CPU'
  buildConfiguration: 'Release'
  
workspace:
  clean: 'all'

steps:
# Restore and Build the solution
- task: DotNetCoreCLI@2
  displayName: 'Build Solution'
  inputs:
    command: 'build'
    projects: '$(solution)'
    arguments: '/m'

# Run unit tests
- task: DotNetCoreCLI@2
  displayName: 'Run Unit Tests'
  inputs:
    command: 'test'
    projects: '$(solution)'

I've tried a few different ways to set the AZURE_PIPELINE variable in the pipeline configuration, but each time the pipeline is run the test fails. My understanding is that the preprocessor directive should ensure the return at the top gets compiled into the test, which should allow it to pass.

I've tried adding that in as an argument to the build task:

- task: DotNetCoreCLI@2
  displayName: 'Build Solution'
  inputs:
    command: 'build'
    projects: '$(solution)'
    arguments: '/m /p:DefineConstants=AZURE_PIPELINE'

I've also tried specifying the environment variable directly in the task. Another post mentioned that environment variables have to be set to the task in which they're being referenced, so I tried setting that explicitly to both my build and test tasks:

- task: DotNetCoreCLI@2
  displayName: 'Build Solution'
  env:
    AZURE_PIPELINE: true
  inputs:
    command: 'build'
    projects: '$(solution)'
    arguments: '/m'

- task: DotNetCoreCLI@2
  displayName: 'Run Unit Tests'
  env:
    AZURE_PIPELINE: true
  inputs:
    command: 'test'
    projects: '$(solution)'

Regardless of what I've tried, that test always fails in the pipeline:

Pipeline Screenshot

What am I missing? Am I misunderstanding how this should work, either the preprocessor directive or my pipeline's variable configuration?

draconastar
  • 385
  • 3
  • 12
  • First off, do you know what a preprocessor directive is and when they are evaluated? Environment variables and preprocessor directives have nothing to do with one another. Do the logs indicate that it's rebuilding when you run `dotnet test`? Because if you set a preprocessor directive on `dotnet build`, then it rebuilds without that directive on `dotnet test`, obviously the results won't be what you're expecting. – Daniel Mann Aug 23 '23 at 19:59
  • @DanielMann I understand the basics, but perhaps I don't understand some of the finer details. My apologies. I got the impression as I was researching this before posting that 'env' is defining variables that get mapped to the process environment and that those can be referenced by preprocessor directives like `#if`. `dotnet test` logs only mention that packages are being restored, so I'm not sure if it's also rebuilding. I would think (or at least hope) that my second attempt where I set the 'env' variable on both tasks would account for that possibility. – draconastar Aug 24 '23 at 01:11

1 Answers1

0

This answer and the article they reference helped me determine the piece that I'm missing. I'll detail some of that here in case it's helpful for others.

Preprocessor directives like #if are looking for symbols, which are what get defined by the /p:DefineConstants compiler option. As @DanielMann alluded to in his comment, these are distinct from environment variables, which can not be directly referenced by those directives.

That means that in order for what I'm doing to work, there needs to be a step during compilation where an environment variable is read and set as a constant. The <DefineConstants> property in my project's .csproj file is what allows that hand off. It's value is referencing an environment variable PipelineFlag:

<PropertyGroup>
    <DefineConstants>$(PipelineFlag)</DefineConstants>
</PropertyGroup>

If the process environment has the PipelineFlag variable defined during build, its value is read and replaced. That allows me to configure my build task like so:

- task: DotNetCoreCLI@2
  displayName: 'Build Solution'
  env:
    PipelineFlag: AZURE_PIPELINE # <--
  inputs:
    command: 'build'
    projects: '$(solution)'
    arguments: '/m'

Which results in the constant's value being replaced during build, effectively something like this:

<PropertyGroup>
    <DefineConstants>AZURE_PIPELINE</DefineConstants>
</PropertyGroup>

With that in place, directives see the symbol as defined and my unit test now returns immediately when running in the pipeline, but runs the actual test everywhere else.

draconastar
  • 385
  • 3
  • 12