loading...

A Potpourri of .Net Core CI/CD (Lite)

jeikabu profile image jeikabu Originally published at rendered-obsolete.github.io on ・1 min read

I have been using Visual Studio professionally for about a decade now. Jenkins for CI/CD for the last 5 years. While the latest iterations of both are excellent tools (Jenkins pipeline, in particular), my development environment feels stagnant.

I use OSX at home, and with our migration to Github now seemed like the perfect time to try something different. What follows is a brain-dump of stuff I’ve done with a recent github project, nng.NETCore- .Net bindings for nng.

dotnet CLI

I’d previously used MonoDevelop on OSX. Post Microsoft acquisition it was rebranded Visual Studio for Mac. While more comfortable than Apple’s Xcode, it always feels… clunky. Enter the dotnet CLI:

mkdir l0core
cd l0core
# Create l0core.sln
dotnet new sln
# Create l0core/l0core.csproj
dotnet new console --name l0core
# Add l0core.csproj to l0core.sln
dotnet sln add l0core
# Add a bunch of existing projects

dotnet add l0core package <package_name>

One thing that surprised me was although you can add an existing project to the sln with:

dotnet sln add ../layer0/Layer0Shared/

You can’t easily add a project reference, for example:

# NB: This doesn't work
# In l0core.csproj add reference to Layer0Shared.csproj
dotnet add l0core reference Layer0Shared

Instead, you have to refer to the actual project:

dotnet add l0core reference ../layer0/Layer0Shared/

In Visual Studio I’m used to adding a project to the solution, then adding references to that project.

Visual Studio Code

Visual Studio Code has been my go-to text editor for over a year now. Especially for editing markdown.

After creating the project, VS Code C# guide mentions debugging and stuff that didn’t work. Down at the bottom, the FAQ mentions .Net: Generate Assets for Build and Debug. That failed with:

Could not locate .NET Core project. Assets were not generated.

Seems to be this bug. In any case, closing and restarting VS Code seemed to fix the issue.

Start Debugging and immediately failing was great:

Starting with 1.26 (now on 1.27.1), after a few weeks using it as my main C# IDE, my gripes are:

  • Intellisense stops working frequently (need to Cmd+Shift+p > restart omnisharp )
  • No “tasks” window
  • Starting debugger or running tests get stuck. End up killall dotnet and restarting VS Code
  • No XML doc assistance (this extension is all there is). In regular Visual Studio:
    • /// starts comment block, and hitting return indents and inserts ///
    • XmlDoc tags like <see> auto-complete and use intellisense

  • Non-trivial yet common types (e.g. System.Assembly) take debugger a while to evaluate

  • Lacks some source code analysis/linting; unneeded using statements, namespace scoping, etc.

Appveyor

Setup Appveyor for both Windows and Linux builds of nng.NETCore.

After adding the project via the web interface, either commiting an empty appveyor.yml or clicking NEW BUILD will trigger a build and tests.

Failed with:

msbuild "C:\projects\nng-netcore\nng.NETCore.sln" /verbosity:minimal /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
Microsoft (R) Build Engine version 14.0.25420.1
Copyright (C) Microsoft Corporation. All rights reserved.
C:\projects\nng-netcore\nng.NETCore\nng.NETCore.csproj(1,1): error MSB4041: The default XML namespace of the project must be the MSBuild XML namespace. If the project is authored in the MSBuild 2003 format, please add xmlns="http://schemas.microsoft.com/developer/msbuild/2003" to the <Project> element. If the project has been authored in the old 1.0 or 1.2 format, please convert it to MSBuild 2003 format.

Appveyor defaults to using msbuild 14.0 which doesn’t support the latest project format. Change appveyor.yml to:

image: Visual Studio 2017

git push (or NEW BUILD ):

C:\Program Files\dotnet\sdk\2.1.401\Sdks\Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets(198,5): error NETSDK1004: Assets file 'C:\projects\nng-netcore\nng.NETCore\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. [C:\projects\nng-netcore\nng.NETCore\nng.NETCore.csproj]
Done Building Project "C:\projects\nng-netcore\nng.NETCore\nng.NETCore.csproj" (default targets) -- FAILED.

Need to restore packages:

before_build:
  - dotnet restore

To reduce the amout of build log-spam:

build:
  verbosity: minimal

Testing with xUnit

Our current project uses NUnit so I’m pretty comfortable with that. xUnit seems to be the only recommended testing framework that isn’t MSTest.

I like using parameterized tests to run variants and cover multiple code paths. A handful of blog posts cover how to do that:

class TransportsClassData : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[] { "ws://localhost:23123" };
        yield return new object[] { "tcp://localhost:23124" };
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

[Theory]
[ClassData(typeof(TransportsClassData))]
public async Task Basic(string url)
{
    // ...
}

Needed the equivalent of Nunit’s SetupFixtureAttribute to perform one-time setup like loading the nng native/unmanaged library:

public class NngCollectionFixture
{
    public NngCollectionFixture()
    {
        // ...
    }
}

[CollectionDefinition("nng")]
public class NngCollection : ICollectionFixture<NngCollectionFixture>
{
}

[Collection("nng")]
public class BusTests
{
    public BusTests(NngCollectionFixture collectionFixture)
    {
        // ...
    }
}

Appveyor automatically picked up all the tests without any additional configuration. See Running tests for more information.

Code Coverage

Using codecov for code coverage. The following references cover everything:

In all projects you want to enable code coverage add:

<PropertyGroup>
  <DebugType>full</DebugType>
</PropertyGroup>

In appveyor.yml:

before_build:
  - dotnet restore
  - choco install opencover.portable
  - choco install codecov

test_script:
  - OpenCover.Console.exe -register:user -target:dotnet.exe -targetargs:test -output:".\coverage.xml" -oldstyle

after_test:
  - codecov -f "coverage.xml" -t 2a07cd3d-8620-4495-8c14-3252b10b90bd
  1. Use Chocolatey to install OpenCover
  2. Run dotnet test with OpenCover
  3. Upload results to codecov

Linux

Round out the CI with a Linux build and both debug/release configurations:

image: 
- Visual Studio 2017
- Ubuntu
configuration:
  - Debug
  - Release

Commands can be prefixed with cmd: or sh: for Windows-only and Linux-only commands, respectively. With neither runs on both. The Chocolately stuff slows down builds quite a bit (adds a few minutes), so only run code coverage on Windows:

before_build:
  - dotnet restore
  - cmd: choco install opencover.portable
  - cmd: choco install codecov

test_script:
  - cmd: OpenCover.Console.exe -register:user -target:dotnet.exe -targetargs:"test --filter platform!=posix" -output:".\coverage.xml" -oldstyle
  - sh: dotnet test --filter "platform!=windows"

Avoid platform-specific tests with --filter platform!=XYZ. In the test code:

[Fact]
[Trait("platform", "windows")]
public void WindowsOnlyTest()
{
    // ...
}

See Filter Option Detailsand Running Selective Unit Tests for additional options.

Deployment

Branches shows how to do branch dependent build configuration without creating multiple appveyor.yml files. Deployment covers general deployment topics.

Wanted to limit deployment to specifc branches when there was a git tag. Pushing artifacts to Github releases shows the appveyor configuration which I then adapted to Nuget:

deploy:
  provider: NuGet
  api_key:
    secure: OSKjxq8SQmVX8UaVkgaq1aUeGnuXHiTzNZoIi2VR0OMCp/WypCkBY7JbkmoKz497
  artifact: /.*\.nupkg/
  on:
    branch: master
    appveyor_repo_tag: true

The api_key value comes from encrypting my nuget upload token.

The obvious problem being I can’t nuget sign without my private key. Might have to settle for deploying to Github releases for now.

Flair

No github project is complete without flair.

NuGet via:

[![NuGet](https://img.shields.io/nuget/v/PACKAGE.svg?colorB=COLOR)](https://www.nuget.org/packages/PACKAGE)

Build status from the shields.io examples:

[![Build status](https://img.shields.io/appveyor/tests/USERNAME/PROJECT/BRANCH.svg)](https://ci.appveyor.com/project/USERNAME/PROJECT/branch/BRANCH)

codecov from Codecov.io repo Settings > Badge :

[![codecov](https://codecov.io/gh/GITHUB_OWNER/PROJECT/branch/BRANCH/graph/badge.svg)](https://codecov.io/gh/GITHUB_OWNER/PROJECT)

Docker

While working on OSX it would be convenient to be able to build/test on Windows and Linux without having to push and wait for Appveyor. Enter Docker.

Create Dockerfile:

FROM microsoft/dotnet:2.1-sdk
WORKDIR /app

COPY . ./
RUN dotnet restore
RUN dotnet test tests --filter platform!=windows

Run:

docker build -t dotnetapp-dev .

Have experimented with Docker in the past. While it was certainly cool, never really went “all in” and made it part of our development pipeline. Now that Windows is supported as a first-class citizen I’ll be giving it another, more serious look.

Posted on Sep 21 '18 by:

Discussion

markdown guide
 

Are those your production codecov and nuget keys/tokens? If so, you'd better expire them since they are now public.

 

Have to admit, I was morbidly curious if anyone would say something about tokens that looked real.

Last time I used something obviously fake. Appveyor (and seemingly every other github et al CI/CD service) provides a way to encrypt sensitive strings like API keys. Not sure how secure it really is, so after I tried it out I changed the token. ;)

For codecov, it's actually the real report upload token. But for public projects such as this you can upload reports without it. Guess they figure there's not much harm that can come from people maliciously uploading fake reports. But that way it doesn't really matter if it gets commited to a public repository.

 

Cool. You seem like an experienced dev so I figured you knew better but we all make mistakes. Good post.

I'm certainly due for a mistake (assuming I haven't made one already).
Thanks for the comments.